diff --git a/.idea/typed-api-spec.iml b/.idea/typed-api-spec.iml
index 69ef67a..d177006 100644
--- a/.idea/typed-api-spec.iml
+++ b/.idea/typed-api-spec.iml
@@ -6,6 +6,8 @@
+
+
diff --git a/docs/docs/01_overview.md b/docs/docs/01_overview.md
new file mode 100644
index 0000000..12072b4
--- /dev/null
+++ b/docs/docs/01_overview.md
@@ -0,0 +1,46 @@
+---
+sidebar_position: 1
+---
+
+# Overview
+
+typed-api-spec is an TypeScript based declarative API specification.
+
+## Features
+
+### TypeScript based API Specification
+You can define your API specification using TypeScript.
+No need to use yaml, json, or any other format.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ responses: { 200: { body: { userNames: string[] }; }; };
+ };
+ };
+}>;
+```
+
+See [API Specification](./03_API%20Specification/specification) page for more details.
+
+### Type-safe, zero-runtime API client
+typed-api-spec provides strict typed [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch).
+For example, if you define API response schema by [API Specification](./03_API%20Specification/specification), you can get the typed response data.
+It is just native [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch), so:
+- zero runtime: 0kb bundle size
+- zero dependencies
+- zero learning cost
+
+```typescript
+const fetchT = fetch as FetchT<"", Spec>;
+const res = await fetchT("/users");
+const data = await res.json(); // data is { userNames: string[] }
+```
+
+See [Client](./04_client/concept.md) page for more details.
+
+### Server Framework & Validation library integration
+typed-api-spec can be integrated with various server frameworks(like [Express](https://expressjs.com/), [Fastify](https://fastify.dev/)) and validation libraries(like [zod](https://zod.dev/), [Valibot](https://valibot.dev/)).
+
+See [Server](/typed-api-spec/docs/category/server) and [Validation](/typed-api-spec/docs/category/validation) page for more details.
\ No newline at end of file
diff --git a/docs/docs/02_getting-started.md b/docs/docs/02_getting-started.md
new file mode 100644
index 0000000..4bbdb64
--- /dev/null
+++ b/docs/docs/02_getting-started.md
@@ -0,0 +1,69 @@
+---
+sidebar_position: 2
+---
+
+# Getting Started
+
+Let's assume you have a npm project with code like this that __fetch__ from GitHub:
+
+```typescript
+const GITHUB_API_ORIGIN = "https://api.github.com";
+const url = `/repos/mpppk/typed-api-spec/topics?page=1`;
+const response = await fetch(`${GITHUB_API_ORIGIN}${url}`);
+if (!response.ok) {
+ // response.json() returns any
+ const { message } = await response.json()
+ return console.error(message);
+}
+// response.json() returns any
+const { names } = await response.json()
+console.log(names); // => ["api-spec", "fetch", "typescript"]
+```
+
+## Installation
+
+```bash
+npm install @mpppk/typed-api-spec
+```
+
+## Define API Spec
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/repos/:owner/:repo/topics": {
+ get: {
+ query: { page?: string };
+ responses: {
+ 200: { body: { names: string[] }; };
+ 400: { body: { message: string; }; };
+ };
+ };
+ };
+}>;
+```
+
+## Add types to fetch function
+
+```typescript
+import { fetchT } from "@mpppk/typed-api-spec";
+const fetch = fetch as FetchT;
+```
+
+## Use fetch function
+
+```typescript {3}
+const GITHUB_API_ORIGIN = "https://api.github.com";
+const url = `/repos/mpppk/typed-api-spec/topics?page=1`;
+const fetchT = fetch as FetchT;
+const response = await fetchT(`${GITHUB_API_ORIGIN}${url}`);
+if (!response.ok) {
+ // reponse.json() is typed as { message: string } because resnose is not ok
+ const { message } = await response.json()
+ return console.error(message);
+}
+// reponse.json() is typed as { names: string[] } because resnose is ok
+const { names } = await response.json()
+console.log(names); // => ["api-spec", "fetch", "typescript"]
+```
+
+Notice that only few (highlighted) lines have been changed from original, but now the __fetch__ is type-safe.
\ No newline at end of file
diff --git a/docs/docs/03_api_specification.md b/docs/docs/03_api_specification.md
new file mode 100644
index 0000000..e28bb03
--- /dev/null
+++ b/docs/docs/03_api_specification.md
@@ -0,0 +1,149 @@
+---
+sidebar_position: 3
+---
+
+# API Specification
+
+API specification is a definition of the API endpoint.
+In typed-api-spec, API specification is written in TypeScript type and has the following structure:
+
+```
+Path --> Method --> Spec
+```
+
+- Path: The path of the API endpoint.
+- Method: The HTTP method of the API endpoint.
+- Spec: The specification of the API endpoint, which includes request parameters, response schema, etc.
+
+For example, if you write the following code:
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ // Path
+ "/users": {
+ // Method
+ get: {
+ // Spec
+ responses: { 200: { body: { userNames: string[] }; }; };
+ };
+ };
+}>;
+```
+
+the above code defines:
+- Path: `/users`
+- Method: `get`
+- Spec: `{ responses: { 200: { body: { userNames: string[] }; }; }; }`
+
+## Structure
+
+### Path
+
+Path is the path of the API endpoint.
+It can contain path parameters like `:id`.
+
+```typescript
+
+type Spec = DefineApiEndpoints<{
+ // Path has path parameter `:id`
+ "/users/:id": {
+ get: {
+ params: { id: string };
+ responses: { 200: { body: { user: { id: string; name: string }; }; }; };
+ };
+ };
+}>;
+```
+
+### Method
+
+Method is the HTTP method of the API endpoint.
+It can be one of the following:
+- `get`
+- `post`
+- `put`
+- `delete`
+- `patch`
+- `head`
+- `options`
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: { responses: { 200: { body: { userNames: string[] }; }; }; };
+ post: { responses: { 200: { body: { userName: string }; }; }; };
+ };
+}>;
+```
+
+### Spec
+
+Spec has the following properties:
+- `params`: The path parameters of the request.
+- `query`: The query parameters of the request.
+- `headers`: The headers of the request.
+- `body`: The body of the request.
+- `responses`: The response schema of the request.
+ - `body`: The body of the response.
+ - `headers`: The headers of the response.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users/:id": {
+ get: {
+ params: { id: string };
+ query: { page?: string };
+ headers: { "x-api-key": string };
+ responses: { 200: {
+ headers: {"content-type": "application/json"};
+ body: { userNames: string[] }; };
+ };
+ };
+ };
+}>;
+```
+
+## Validation library integration
+
+typed-api-spec can be integrated with various validation libraries.
+For example, you can use zod to define the schema of the request and response.
+
+```typescript
+import { z } from "zod";
+import { ZodApiEndpoints } from "./index";
+
+const Spec = {
+ "/users/:id": {
+ get: {
+ params: { id: z.string() },
+ query: { page: z.string().optional() },
+ headers: { "x-api-key": z.string() },
+ responses: {
+ 200: {
+ headers: { "content-type": z.literal("application/json") },
+ body: { userNames: z.array(z.string()) },
+ }
+ },
+ },
+ },
+} satisfies ZodApiEndpoints
+```
+
+For more information, see the [Validation](/typed-api-spec/docs/category/validation) page.
+
+## API
+
+### DefineApiEndpoints
+
+DefineApiEndpoints is a utility type that defines the API specification.
+If you write wrong API specification, DefineApiEndpoints will throw a type error.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: { responses: { 200: { body: { userNames: string[] }; }; }; };
+ };
+}>;
+```
+
+TODO: Add StackBlitz demo
\ No newline at end of file
diff --git a/docs/docs/04_client.md b/docs/docs/04_client.md
new file mode 100644
index 0000000..ecb129b
--- /dev/null
+++ b/docs/docs/04_client.md
@@ -0,0 +1,178 @@
+---
+sidebar_position: 4
+---
+
+# Client(zero-fetch)
+
+typed-api-spec provides `zero-fetch`, a type-safe, zero-runtime API client.
+
+:::info[What does **zero-runtime** mean?]
+
+zero-fetch just add type information to native fetch, and does not add any runtime code.
+Type information is erased during compilation, so it does not affect the runtime behavior.
+As a result, it does not increase bundle size and does not have any runtime dependencies.
+
+````typescript
+// fetchT is just native fetch, so it does not have any additional runtime dependencies
+const fetchT = fetch as FetchT<"", Spec>;
+````
+
+:::
+
+## Type-safe features
+
+### Response
+
+zero-fetch provides type information for the response data based on the API specification.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ responses: { 200: { body: { names: string[] }; }; };
+ };
+ };
+}>;
+
+const fetchT = fetch as FetchT<"", Spec>;
+const res = await fetchT("/users");
+const data = await res.json(); // data is { userNames: string[] }
+```
+
+If the response have multiple status codes, response type is union of each status code type.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ responses: {
+ 200: { body: { names: string[] }; };
+ 201: { body: { ok: boolean }; };
+ 400: { body: { message: string; }; };
+ 500: { body: { internalError: string; }; };
+ };
+ };
+ }
+}>;
+
+const fetchT = fetch as FetchT<"", Spec>;
+const res = await fetchT("/users");
+if (!res.ok) {
+ // If res.ok is false, status code is 400 or 500
+ // So res.json() returns { message: string } | { internalError: string }
+ const data = await res.json();
+ return console.error(data);
+}
+// If res.ok is true, status code is 200 or 201
+// So res.json() returns { names: string[] } | { ok: boolean }
+const data = await res.json(); // names is string[]
+console.log(data);
+```
+
+### Path & Path parameters
+
+zero-fetch accepts only the path that is defined in the API specification.
+Path parameters are also supported as `:paramName` in the path.
+
+```typescript
+
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: { responses: { 200: { body: { names: string[] }; }; }; };
+ };
+ "/users/:id": {
+ get: { responses: { 200: { body: { name: string }; }; }; };
+ };
+}>;
+const fetchT = fetch as FetchT<"", Spec>;
+
+await fetchT("/users"); // OK
+await fetchT("/users/1"); // OK
+await fetchT("/posts"); // Error: Argument of type '"/posts"' is not assignable to parameter of type '"/users" | "/users/:id"'.
+await fetchT("/users/1/2"); // Error: Argument of type '"/users/1/2"' is not assignable to parameter of type '"/users" | "/users/:id"'.
+```
+
+### Query
+
+zero-fetch accepts only the query parameters that are defined in the API specification.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ query: { page: string };
+ responses: { 200: { body: { names: string[] }; }; };
+ };
+ };
+}>;
+
+const fetchT = fetch as FetchT<"", Spec>;
+await fetchT("/users?page=1"); // OK
+await fetchT("/users"); // Error: Argument of type '"/users"' is not assignable to parameter of type '"/users?page=${string}"'.
+```
+
+### headers
+
+zero-fetch accepts only the headers that are defined in the API specification.
+
+```typescript
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ headers: { "x-api-key": string };
+ responses: { 200: { body: { names: string[] }; }; };
+ };
+ };
+}>;
+const fetchT = fetch as FetchT<"", Spec>;
+
+await fetchT("/users", { headers: { "x-api-key": "key" } }); // OK
+await fetchT("/users", { headers: {} }); // Error: Type {} is not assignable to type '{ "x-api-key": string; }'.
+```
+
+### body
+
+zero-fetch accepts only the body that is defined in the API specification.
+Please note that when converting an object to a string, you must use the `JSONT` type provided by typed-api-spec.
+
+```typescript
+import { JSONT } from "@mpppk/typed-api-spec/json";
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ post: {
+ body: { name: string };
+ responses: { 200: { body: { id: string }; }; };
+ };
+ };
+}>;
+const fetchT = fetch as FetchT<"", Spec>;
+const JSONT = JSON as JSONT;
+
+await fetchT("/users", { method: "POST", body: JSONT.stringify({ name: "name" }) }); // OK
+await fetchT("/users", { method: "POST", body: JSONT.stringify({ name: 1 }) }); // Error: Type TypedString<{ userName: number; }> is not assignable to type TypedString<{ userName: string; }>
+```
+
+## API
+
+### FetchT
+
+FetchT is a type that adds type information to native fetch.
+First generic parameter is the origin of the API server, and the second generic parameter is the API specification.
+
+```typescript
+const fetchT = fetch as FetchT<"https://api.example.com", Spec>;
+```
+
+### JSONT
+
+JSONT is a type that adds type information to native JSON.
+If you want to check body parameter type, you need to use JSONT.stringify to convert object to string.
+
+```typescript
+import { JSONT } from "@mpppk/typed-api-spec/json";
+const JSONT = JSON as JSONT;
+// body parameter type will be checked by using JSONT.stringify
+await fetchT("/users", { method: "POST", body: JSONT.stringify({ name: "name" }) });
+```
+
+
diff --git a/docs/docs/05_server/_category_.json b/docs/docs/05_server/_category_.json
new file mode 100644
index 0000000..e3b9b0f
--- /dev/null
+++ b/docs/docs/05_server/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "Server",
+ "position": 5,
+ "link": {
+ "type": "generated-index",
+ "description": "typed-api-spec provides server integrations that support implementing handler based on API Specification."
+ }
+}
diff --git a/docs/docs/05_server/express.md b/docs/docs/05_server/express.md
new file mode 100644
index 0000000..5354e00
--- /dev/null
+++ b/docs/docs/05_server/express.md
@@ -0,0 +1,83 @@
+# Express
+
+Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
+
+
+typed-api-spec provides first-class support for Express.
+You can apply types to your Express app using the `typed()`.
+
+Here is an example of how to use `express` with `typed-api-spec` and `zod`.
+
+```typescript
+const app = express();
+app.use(express.json());
+const wApp = typed(pathMap, app);
+wApp.get("/users", (req, res) => {
+// validate method is available in res.locals
+// validate(req).query() is equals to pathMap["/users"]["get"].query.safeParse(req.query)
+const { data, error } = res.locals.validate(req).query();
+if (data !== undefined) {
+ // res.status(200).json() accepts only the response schema defined in pathMap["/users"]["get"].res["200"]
+ res.status(200).json({ userNames: [`page${data.page}#user1`] });
+} else {
+ // res.status(400).json() accepts only the response schema defined in pathMap["/users"]["get"].res["400"]
+ res.status(400).json({ errorMessage: error.toString() });
+}
+});
+
+newApp().listen(3000, () => { console.log(`Example app listening on port ${port}`); });
+```
+
+## API
+
+### typed()
+
+typed() is a function that applies more strict types to the Express app.
+It returns an Express app same as the input app, but validate method has been added to the request locals.
+
+Note that the validate method is depended on validation library you use.
+Following example uses zod.
+
+```typescript
+import { ZodApiEndpoints } from "@mpppk/typed-api-spec/zod";
+import { typed } from "@mpppk/typed-api-spec/express/zod";
+import { z } from "zod";
+
+const Spec = {
+ "/users": {
+ get: {
+ query: z.object({ page: z.string() }),
+ responses: { 200: { body: z.object({ userNames: z.string().array() }) } }
+ }
+ }
+} satisfies ZodApiEndpoints
+
+const wApp = typed(pathMap, app);
+wApp.get("users", (req, res) => {
+ // validate method is available in res.locals because of typed()
+ const { data, error } = res.locals.validate(req).query();
+})
+```
+
+### validate()
+
+validate() is a method that is added to the request locals by typed().
+It returns a function that validates the request parameters and returns the result.
+Available methods are `query()`, `params()`, `headers()`, and `body()`.
+
+### asAsync()
+
+asAsync() is a function that wraps the express app to handle errors of async handlers.
+If error is thrown in async handler of wrapped app, it will be caught and passed to the error handler.
+
+:::note
+
+The reason why we provide this function is that Express4 or lower does not properly handle async errors.
+The upcoming Express5 release will support async error handling, making this method unnecessary.
+
+:::
+
+## Supported validation libraries
+
+* [zod](/typed-api-spec/docs/validation/zod)
+* [valibot](/typed-api-spec/docs/validation/valibot)
\ No newline at end of file
diff --git a/docs/docs/05_server/fastify.md b/docs/docs/05_server/fastify.md
new file mode 100644
index 0000000..5d3039b
--- /dev/null
+++ b/docs/docs/05_server/fastify.md
@@ -0,0 +1,62 @@
+# Fastify
+
+Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture.
+
+Here is an example of how to use `fastify` with `typed-api-spec` and `zod`.
+
+```typescript
+const fastify = Fastify({ logger: true });
+
+fastify.setValidatorCompiler(validatorCompiler);
+fastify.setSerializerCompiler(serializerCompiler);
+const server = fastify.withTypeProvider();
+
+const routes = toRoutes(pathMap);
+
+server.route({
+ ...routes["/users"]["get"],
+ handler: async (request, reply) => {
+ const page = request.query.page;
+ return { userNames: [`page${page}#user1`] };
+ },
+});
+
+await fastify.listen({ port: 3000 });
+```
+
+## API
+
+### toRoutes()
+
+toRoutes() is a function that converts the API specification schema to Fastify route object.
+
+```typescript
+
+const Spec = {
+ "/users": {
+ get: {
+ query: z.object({ page: z.string() }),
+ responses: { 200: { body: z.object({ userNames: z.string().array() }) }},
+ },
+ },
+} satisfies ZodApiEndpoints
+
+const routes = toRoutes(Spec);
+console.log(routes["/users"]["get"])
+/* =>
+{
+ method: 'GET',
+ url: '/users',
+ schema: {
+ query: z.object({ page: { type: 'string' } }),
+ response: {
+ 200: { z.object({ userNames: z.string().array() } }
+ }
+ }
+}
+*/
+```
+
+## Supported validation libraries
+
+* [zod](/typed-api-spec/docs/validation/zod)
diff --git a/docs/docs/06_validation/_category_.json b/docs/docs/06_validation/_category_.json
new file mode 100644
index 0000000..e58be27
--- /dev/null
+++ b/docs/docs/06_validation/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "Validation",
+ "position": 6,
+ "link": {
+ "type": "generated-index",
+ "description": "You can use your favorite validation library with typed-api-spec."
+ }
+}
diff --git a/docs/docs/06_validation/concept.md b/docs/docs/06_validation/concept.md
new file mode 100644
index 0000000..184dd7c
--- /dev/null
+++ b/docs/docs/06_validation/concept.md
@@ -0,0 +1,93 @@
+---
+sidebar_position: 0
+---
+
+# Overview
+
+The most basic way to define an API specification in typed-api-spec is using TypeScript types.
+However, you may want to perform runtime validation to check that request parameters are provided in the expected type.
+
+The simplest way is to use only the type information provided by a validation library. For exmple, you can use [zod](https://zod.dev) to define the API specification of typed-api-spec.
+
+```typescript
+
+import { z } from "zod";
+
+const User = z.object({
+ id: z.string(),
+ name: z.string(),
+});
+type User = z.infer;
+
+const UsersQuery = z.object({
+ page: z.string(),
+});
+
+type Spec = DefineApiEndpoints<{
+ "/users": {
+ get: {
+ query: UsersQuery;
+ responses: { 200: { body: User } };
+ };
+ };
+}>;
+```
+
+In this way, you can completely separate runtime validation and API definition, allowing you to use any validation library.
+To validate, just use the validation library. Following code is an example of how to validate request parameters using zod and express.
+
+```typescript
+const app = Express();
+app.get("users", (req, res) => {
+ const usersQuery = UsersQuery.parse(req.query)
+})
+```
+
+This is not bad, but the problem is that there is no guarantee that the UsersQuery is actually the expected schema. For example, you may accidentally write something like `User.parse(req.query)`.
+(`User` is expected as a response schema, not a query.)
+
+typed-api-spec provides a way to define the API specification using the validation library directly.
+
+```typescript
+import { z } from "zod";
+
+const Spec = {
+ "/users": {
+ get: {
+ query: z.object({ page: z.string() }),
+ responses: { 200: { body: z.object({ id: z.string(), name: z.string() }) } }
+ }
+ }
+} satisfies ZodApiEndpoints
+```
+
+In this way, you can use validate function directly.
+
+```typescript
+const app = Express();
+app.get("users", (req, res) => {
+ const usersQuery = Spec["/users"]["get"].query.parse(req.query)
+})
+```
+
+:::note[Server integration]
+
+You can make it even easier by using some server-oriented integrations such as [Express](/typed-api-spec/docs/server/express) and [Fastify]((/typed-api-spec/docs/server/fastify)).
+
+```typescript
+const app = express();
+const wApp = typed(Spec, app);
+wApp.get("users", (req, res) => {
+ const { data, error } = res.locals.validate(req).query();
+})
+```
+
+For more information, see the [Server](/typed-api-spec/docs/category/server) page.
+
+:::
+
+:::note[You need validate explicitly if you want]
+
+Even if you define API Specification with a validation library, typed-api-spec does not validate the request parameters automatically.
+You need to explicitly perform validation, whether on the server side or on the client side.
+:::
diff --git a/docs/docs/06_validation/valibot.md b/docs/docs/06_validation/valibot.md
new file mode 100644
index 0000000..19dd3d2
--- /dev/null
+++ b/docs/docs/06_validation/valibot.md
@@ -0,0 +1,33 @@
+---
+sidebar_position: 2
+---
+
+# Valibot
+
+[valibot](https://valibot.dev ) is the open source schema library for TypeScript with bundle size, type safety and developer experience in mind.
+You can use valibot to define the API specification of typed-api-spec.
+
+```typescript
+import { v } from "valibot";
+
+const Spec = {
+ "/users/:id": {
+ get: {
+ params: v.object({ id: v.string() }),
+ query: v.object({ page: v.string().optional() }),
+ headers: v.object({ "x-api-key": v.string() }),
+ responses: {
+ 200: {
+ headers: v.object({ "content-type": v.literal("application/json") }),
+ body: v.object({ userNames: v.array(v.string()) }),
+ }
+ },
+ },
+ },
+} satisfies ZodApiEndpoints
+```
+
+### Using server integration
+
+If you use `express`, you can use official integration to validate request parameters based on the API specification.
+See the [express](/typed-api-spec/docs/server/express) page for more information.
\ No newline at end of file
diff --git a/docs/docs/06_validation/write.md b/docs/docs/06_validation/write.md
new file mode 100644
index 0000000..e15af4e
--- /dev/null
+++ b/docs/docs/06_validation/write.md
@@ -0,0 +1,6 @@
+# Write your own integration
+
+If you want to use a validation library which is not supported by `typed-api-spec`, you can write your own integration.
+typed-api-spec provides a utility type `DefineApiSpec`, `DefineApiResponses`, and others for defining API specification schema with validation library.
+
+TBW
\ No newline at end of file
diff --git a/docs/docs/06_validation/zod.md b/docs/docs/06_validation/zod.md
new file mode 100644
index 0000000..ceb5305
--- /dev/null
+++ b/docs/docs/06_validation/zod.md
@@ -0,0 +1,33 @@
+---
+sidebar_position: 1
+---
+
+# zod
+
+[zod](https://zod.dev) is a TypeScript-first schema declaration and validation library.
+You can use zod to define the API specification of typed-api-spec.
+
+```typescript
+import { z } from "zod";
+
+const Spec = {
+ "/users/:id": {
+ get: {
+ params: z.object({ id: z.string() }),
+ query: z.object({ page: z.string().optional() }),
+ headers: z.object({ "x-api-key": z.string() }),
+ responses: {
+ 200: {
+ headers: z.object({ "content-type": z.literal("application/json") }),
+ body: z.object({ userNames: z.array(z.string()) }),
+ }
+ },
+ },
+ },
+} satisfies ZodApiEndpoints
+```
+
+## Using server integration
+
+If you use `express` or `fastify`, you can use official integration to validate request parameters based on the API specification.
+See the [express](/typed-api-spec/docs/server/express) or [fastify](/typed-api-spec/docs/server/fastify) page for more information.
diff --git a/docs/docs/intro.md b/docs/docs/intro.md
deleted file mode 100644
index 45e8604..0000000
--- a/docs/docs/intro.md
+++ /dev/null
@@ -1,47 +0,0 @@
----
-sidebar_position: 1
----
-
-# Tutorial Intro
-
-Let's discover **Docusaurus in less than 5 minutes**.
-
-## Getting Started
-
-Get started by **creating a new site**.
-
-Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
-
-### What you'll need
-
-- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
- - When installing Node.js, you are recommended to check all checkboxes related to dependencies.
-
-## Generate a new site
-
-Generate a new Docusaurus site using the **classic template**.
-
-The classic template will automatically be added to your project after you run the command:
-
-```bash
-npm init docusaurus@latest my-website classic
-```
-
-You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
-
-The command also installs all necessary dependencies you need to run Docusaurus.
-
-## Start your site
-
-Run the development server:
-
-```bash
-cd my-website
-npm run start
-```
-
-The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
-
-The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
-
-Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
diff --git a/docs/docs/tutorial-basics/_category_.json b/docs/docs/tutorial-basics/_category_.json
deleted file mode 100644
index 2e6db55..0000000
--- a/docs/docs/tutorial-basics/_category_.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "label": "Tutorial - Basics",
- "position": 2,
- "link": {
- "type": "generated-index",
- "description": "5 minutes to learn the most important Docusaurus concepts."
- }
-}
diff --git a/docs/docs/tutorial-basics/congratulations.md b/docs/docs/tutorial-basics/congratulations.md
deleted file mode 100644
index 04771a0..0000000
--- a/docs/docs/tutorial-basics/congratulations.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-sidebar_position: 6
----
-
-# Congratulations!
-
-You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
-
-Docusaurus has **much more to offer**!
-
-Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
-
-Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
-
-## What's next?
-
-- Read the [official documentation](https://docusaurus.io/)
-- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
-- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
-- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
-- Add a [search bar](https://docusaurus.io/docs/search)
-- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
-- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
diff --git a/docs/docs/tutorial-basics/create-a-blog-post.md b/docs/docs/tutorial-basics/create-a-blog-post.md
deleted file mode 100644
index 550ae17..0000000
--- a/docs/docs/tutorial-basics/create-a-blog-post.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-sidebar_position: 3
----
-
-# Create a Blog Post
-
-Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
-
-## Create your first Post
-
-Create a file at `blog/2021-02-28-greetings.md`:
-
-```md title="blog/2021-02-28-greetings.md"
----
-slug: greetings
-title: Greetings!
-authors:
- - name: Joel Marcey
- title: Co-creator of Docusaurus 1
- url: https://github.com/JoelMarcey
- image_url: https://github.com/JoelMarcey.png
- - name: Sébastien Lorber
- title: Docusaurus maintainer
- url: https://sebastienlorber.com
- image_url: https://github.com/slorber.png
-tags: [greetings]
----
-
-Congratulations, you have made your first post!
-
-Feel free to play around and edit this post as much as you like.
-```
-
-A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
diff --git a/docs/docs/tutorial-basics/create-a-document.md b/docs/docs/tutorial-basics/create-a-document.md
deleted file mode 100644
index 5638bb6..0000000
--- a/docs/docs/tutorial-basics/create-a-document.md
+++ /dev/null
@@ -1,57 +0,0 @@
----
-sidebar_position: 2
----
-
-# Create a Document
-
-Documents are **groups of pages** connected through:
-
-- a **sidebar**
-- **previous/next navigation**
-- **versioning**
-
-## Create your first Doc
-
-Create a Markdown file at `docs/hello.md`:
-
-```md title="docs/hello.md"
-# Hello
-
-This is my **first Docusaurus document**!
-```
-
-A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
-
-## Configure the Sidebar
-
-Docusaurus automatically **creates a sidebar** from the `docs` folder.
-
-Add metadata to customize the sidebar label and position:
-
-```md title="docs/hello.md" {1-4}
----
-sidebar_label: "Hi!"
-sidebar_position: 3
----
-
-# Hello
-
-This is my **first Docusaurus document**!
-```
-
-It is also possible to create your sidebar explicitly in `sidebars.js`:
-
-```js title="sidebars.js"
-export default {
- tutorialSidebar: [
- "intro",
- // highlight-next-line
- "hello",
- {
- type: "category",
- label: "Tutorial",
- items: ["tutorial-basics/create-a-document"],
- },
- ],
-};
-```
diff --git a/docs/docs/tutorial-basics/create-a-page.md b/docs/docs/tutorial-basics/create-a-page.md
deleted file mode 100644
index a2cb8ed..0000000
--- a/docs/docs/tutorial-basics/create-a-page.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-sidebar_position: 1
----
-
-# Create a Page
-
-Add **Markdown or React** files to `src/pages` to create a **standalone page**:
-
-- `src/pages/index.js` → `localhost:3000/`
-- `src/pages/foo.md` → `localhost:3000/foo`
-- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
-
-## Create your first React Page
-
-Create a file at `src/pages/my-react-page.js`:
-
-```jsx title="src/pages/my-react-page.js"
-import React from "react";
-import Layout from "@theme/Layout";
-
-export default function MyReactPage() {
- return (
-
- My React page
- This is a React page
-
- );
-}
-```
-
-A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
-
-## Create your first Markdown Page
-
-Create a file at `src/pages/my-markdown-page.md`:
-
-```mdx title="src/pages/my-markdown-page.md"
-# My Markdown page
-
-This is a Markdown page
-```
-
-A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
diff --git a/docs/docs/tutorial-basics/deploy-your-site.md b/docs/docs/tutorial-basics/deploy-your-site.md
deleted file mode 100644
index 1c50ee0..0000000
--- a/docs/docs/tutorial-basics/deploy-your-site.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-sidebar_position: 5
----
-
-# Deploy your site
-
-Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
-
-It builds your site as simple **static HTML, JavaScript and CSS files**.
-
-## Build your site
-
-Build your site **for production**:
-
-```bash
-npm run build
-```
-
-The static files are generated in the `build` folder.
-
-## Deploy your site
-
-Test your production build locally:
-
-```bash
-npm run serve
-```
-
-The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
-
-You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
diff --git a/docs/docs/tutorial-basics/markdown-features.mdx b/docs/docs/tutorial-basics/markdown-features.mdx
deleted file mode 100644
index 0c59028..0000000
--- a/docs/docs/tutorial-basics/markdown-features.mdx
+++ /dev/null
@@ -1,153 +0,0 @@
----
-sidebar_position: 4
----
-
-# Markdown Features
-
-Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
-
-## Front Matter
-
-Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
-
-```text title="my-doc.md"
-// highlight-start
----
-id: my-doc-id
-title: My document title
-description: My document description
-slug: /my-custom-url
----
-// highlight-end
-
-## Markdown heading
-
-Markdown text with [links](./hello.md)
-```
-
-## Links
-
-Regular Markdown links are supported, using url paths or relative file paths.
-
-```md
-Let's see how to [Create a page](/create-a-page).
-```
-
-```md
-Let's see how to [Create a page](./create-a-page.md).
-```
-
-**Result:** Let's see how to [Create a page](./create-a-page.md).
-
-## Images
-
-Regular Markdown images are supported.
-
-You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
-
-```md
-
-```
-
-
-
-You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
-
-```md
-
-```
-
-## Code Blocks
-
-Markdown code blocks are supported with Syntax highlighting.
-
-````md
-```jsx title="src/components/HelloDocusaurus.js"
-function HelloDocusaurus() {
- return Hello, Docusaurus!
;
-}
-```
-````
-
-```jsx title="src/components/HelloDocusaurus.js"
-function HelloDocusaurus() {
- return Hello, Docusaurus!
;
-}
-```
-
-## Admonitions
-
-Docusaurus has a special syntax to create admonitions and callouts:
-
-```md
-:::tip My tip
-
-Use this awesome feature option
-
-:::
-
-:::danger Take care
-
-This action is dangerous
-
-:::
-```
-
-:::tip My tip
-
-Use this awesome feature option
-
-:::
-
-:::danger Take care
-
-This action is dangerous
-
-:::
-
-## MDX and React Components
-
-[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
-
-```jsx
-export const Highlight = ({children, color}) => (
- {
- alert(`You clicked the color ${color} with label ${children}`)
- }}>
- {children}
-
-);
-
-This is Docusaurus green !
-
-This is Facebook blue !
-```
-
-export const Highlight = ({ children, color }) => (
- {
- alert(`You clicked the color ${color} with label ${children}`);
- }}
- >
- {children}
-
-);
-
-This is Docusaurus green !
-
-This is Facebook blue !
diff --git a/docs/docs/tutorial-extras/_category_.json b/docs/docs/tutorial-extras/_category_.json
deleted file mode 100644
index a8ffcc1..0000000
--- a/docs/docs/tutorial-extras/_category_.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "label": "Tutorial - Extras",
- "position": 3,
- "link": {
- "type": "generated-index"
- }
-}
diff --git a/docs/docs/tutorial-extras/img/docsVersionDropdown.png b/docs/docs/tutorial-extras/img/docsVersionDropdown.png
deleted file mode 100644
index 97e4164..0000000
Binary files a/docs/docs/tutorial-extras/img/docsVersionDropdown.png and /dev/null differ
diff --git a/docs/docs/tutorial-extras/img/localeDropdown.png b/docs/docs/tutorial-extras/img/localeDropdown.png
deleted file mode 100644
index e257edc..0000000
Binary files a/docs/docs/tutorial-extras/img/localeDropdown.png and /dev/null differ
diff --git a/docs/docs/tutorial-extras/manage-docs-versions.md b/docs/docs/tutorial-extras/manage-docs-versions.md
deleted file mode 100644
index 9ea672c..0000000
--- a/docs/docs/tutorial-extras/manage-docs-versions.md
+++ /dev/null
@@ -1,55 +0,0 @@
----
-sidebar_position: 1
----
-
-# Manage Docs Versions
-
-Docusaurus can manage multiple versions of your docs.
-
-## Create a docs version
-
-Release a version 1.0 of your project:
-
-```bash
-npm run docusaurus docs:version 1.0
-```
-
-The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
-
-Your docs now have 2 versions:
-
-- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
-- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
-
-## Add a Version Dropdown
-
-To navigate seamlessly across versions, add a version dropdown.
-
-Modify the `docusaurus.config.js` file:
-
-```js title="docusaurus.config.js"
-export default {
- themeConfig: {
- navbar: {
- items: [
- // highlight-start
- {
- type: "docsVersionDropdown",
- },
- // highlight-end
- ],
- },
- },
-};
-```
-
-The docs version dropdown appears in your navbar:
-
-
-
-## Update an existing version
-
-It is possible to edit versioned docs in their respective folder:
-
-- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
-- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
diff --git a/docs/docs/tutorial-extras/translate-your-site.md b/docs/docs/tutorial-extras/translate-your-site.md
deleted file mode 100644
index cf3a0c4..0000000
--- a/docs/docs/tutorial-extras/translate-your-site.md
+++ /dev/null
@@ -1,88 +0,0 @@
----
-sidebar_position: 2
----
-
-# Translate your site
-
-Let's translate `docs/intro.md` to French.
-
-## Configure i18n
-
-Modify `docusaurus.config.js` to add support for the `fr` locale:
-
-```js title="docusaurus.config.js"
-export default {
- i18n: {
- defaultLocale: "en",
- locales: ["en", "fr"],
- },
-};
-```
-
-## Translate a doc
-
-Copy the `docs/intro.md` file to the `i18n/fr` folder:
-
-```bash
-mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
-
-cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
-```
-
-Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
-
-## Start your localized site
-
-Start your site on the French locale:
-
-```bash
-npm run start -- --locale fr
-```
-
-Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
-
-:::caution
-
-In development, you can only use one locale at a time.
-
-:::
-
-## Add a Locale Dropdown
-
-To navigate seamlessly across languages, add a locale dropdown.
-
-Modify the `docusaurus.config.js` file:
-
-```js title="docusaurus.config.js"
-export default {
- themeConfig: {
- navbar: {
- items: [
- // highlight-start
- {
- type: "localeDropdown",
- },
- // highlight-end
- ],
- },
- },
-};
-```
-
-The locale dropdown now appears in your navbar:
-
-
-
-## Build your localized site
-
-Build your site for a specific locale:
-
-```bash
-npm run build -- --locale fr
-```
-
-Or build your site to include all the locales at once:
-
-```bash
-npm run build
-```
diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts
index a8e0d90..3daa680 100644
--- a/docs/docusaurus.config.ts
+++ b/docs/docusaurus.config.ts
@@ -8,7 +8,7 @@ const config: Config = {
"TypeScript based declarative API specification and zero-runtime client",
favicon: "img/favicon.ico",
- // Set the production url of your site here
+ // Set the poduction url of your site here
url: "https://your-docusaurus-site.example.com",
// Set the // pathname under which your site is served
// For GitHub pages deployment, it is often '//'
diff --git a/docs/static/img/typed-api-spec.png b/docs/static/img/typed-api-spec.png
new file mode 100644
index 0000000..bb085f1
Binary files /dev/null and b/docs/static/img/typed-api-spec.png differ
diff --git a/examples/github/github.ts b/examples/github/github.ts
new file mode 100644
index 0000000..fdb648a
--- /dev/null
+++ b/examples/github/github.ts
@@ -0,0 +1,54 @@
+import { DefineApiEndpoints, FetchT } from "../../src";
+
+const GITHUB_API_ORIGIN = "https://api.github.com";
+
+// There are more headers, but I omit them for simplicity
+type ResponseHeaders = Record<
+ "x-ratelimit-remaining" | "x-ratelimit-reset" | "x-github-request-id",
+ string
+>;
+
+// See https://docs.github.com/ja/rest/repos/repos?apiVersion=2022-11-28#get-all-repository-topics
+type Spec = DefineApiEndpoints<{
+ "/repos/:owner/:repo/topics": {
+ get: {
+ query: { page?: string };
+ headers: {
+ Accept?: "application/vnd.github+json";
+ Authorization?: `Bearer ${string}`;
+ "X-GitHub-Api-Version"?: "2022-11-28";
+ };
+ responses: {
+ 200: {
+ body: { names: string[] };
+ headers: ResponseHeaders;
+ };
+ 400: {
+ body: {
+ message: string;
+ errors: string;
+ documentation_url: string;
+ status: number;
+ };
+ headers: ResponseHeaders;
+ };
+ };
+ };
+ };
+}>;
+const fetchT = fetch as FetchT;
+
+const main = async () => {
+ const response = await fetchT(
+ `${GITHUB_API_ORIGIN}/repos/mpppk/typed-api-spec/topics?page=1`,
+ { headers: { Accept: "application/vnd.github+json" } },
+ );
+ if (!response.ok) {
+ const { message } = await response.json();
+ return console.error(message);
+ }
+ const { names } = await response.json();
+ console.log(names);
+};
+
+main();
diff --git a/src/express/zod.ts b/src/express/zod.ts
index 393c318..988146b 100644
--- a/src/express/zod.ts
+++ b/src/express/zod.ts
@@ -51,8 +51,8 @@ export type ToValidatorsMap = {
* ```
* const router = typed(pathMap, express.Router())
* router.get('/path', (req, res) => {
- * const r = res.locals.validate(req).query()
- * if (!r.success) {
+ * const {data, error} = res.locals.validate(req).query()
+ * if (error) {
* return res.status(400).json({ message: 'Invalid query' })
* }
* return res.status(200).json({ message: 'success', value: r.data.value })