Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 73 additions & 126 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,66 +78,53 @@ try {
</details>

<details>
<summary><a href="https://www.npmjs.com/package/feature-fetch" target="_blank" rel="noreferrer">feature-fetch</a> by <a href="https://builder.group" target="_blank" rel="noreferrer">builder.group</a></summary>
<summary><a href="https://www.npmjs.com/package/feature-fetch" target="_blank" rel="noreferrer">feature-fetch</a> by <a href="https://github.com/builder-group/community" target="_blank" rel="noreferrer">builder.group</a></summary>

`feature-fetch` fits when you want OpenAPI-typed requests with explicit success and error branches. It types path strings, params, request bodies, and response data from generated `paths`. Each request returns `[ok, error, data]`, so failures stay visible at the call site and success branches narrow data. Retry, cache, hooks, and middleware can be added when the client needs production behavior.

::: code-group

```ts [test/my-project.ts]
import { createOpenApiFetchClient } from 'feature-fetch';
import type { paths } from './my-openapi-3-schema'; // generated by openapi-typescript
import {
createOpenApiFetchClient,
hasStatusCode,
retryFeature,
} from "feature-fetch";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

// Create the OpenAPI fetch client
const fetchClient = createOpenApiFetchClient<paths>({
prefixUrl: 'https://myapi.dev/v1'
});
const api = createOpenApiFetchClient<paths>({
baseUrl: "https://myapi.dev/v1",
}).with(retryFeature({ maxRetries: 3 }));

// Send a GET request
const response = await fetchClient.get('/blogposts/{post_id}', {
const [isPostOk, postErr, post] = await api.get("/blogposts/{post_id}", {
pathParams: {
post_id: '123',
post_id: "123",
},
});

// Handle the response (Approach 1: Standard if-else)
if (response.isOk()) {
const data = response.value.data;
console.log(data); // Handle successful response
if (isPostOk) {
console.log(post); // typed from the 2XX response schema
} else if (hasStatusCode(postErr, 404)) {
console.error("Post not found");
} else {
const error = response.error;
if (error instanceof NetworkError) {
console.error('Network error:', error.message);
} else if (error instanceof RequestError) {
console.error('Request error:', error.message, 'Status:', error.status);
} else {
console.error('Service error:', error.message);
}
console.error(postErr.message); // NetworkError | HttpError | FetchError
}

// Send a PUT request
const putResponse = await fetchClient.put('/blogposts', {
const [isUpdateOk, updateErr] = await api.put("/blogposts", {
body: {
title: 'My New Post',
title: "My New Post", // checked against the request body schema
},
});

// Handle the response (Approach 2: Try-catch)
try {
const putData = putResponse.unwrap().data;
console.log(putData); // Handle the successful response
} catch (error) {
// Handle the error
if (error instanceof NetworkError) {
console.error('Network error:', error.message);
} else if (error instanceof RequestError) {
console.error('Request error:', error.message, 'Status:', error.status);
} else {
console.error('Service error:', error.message);
}
if (!isUpdateOk) {
console.error(updateErr.message);
}
```

:::

[Full example](https://github.com/builder-group/community/tree/develop/examples/feature-fetch/vanilla/basic)

</details>

<details>
Expand Down Expand Up @@ -259,126 +246,86 @@ TypeChecking in server environments can be tricky, as you’re often querying da

## Hono with [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router)

[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) provides full type-safety and runtime validation for your HonoAPI routes by wrapping a [Hono router](https://hono.dev/docs/api/routing):

::: tip Good to Know

While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both:
- Types verify your code matches the OpenAPI spec during development
- Validators ensure incoming requests match the spec at runtime

:::
[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) fits when server code should stay aligned with the OpenAPI document. It wraps a [Hono router](https://hono.dev/docs/api/routing) and lets route registration use OpenAPI path strings such as `/pet/{petId}`. TypeScript rejects unknown paths, wrong methods, missing required schemas, and JSON success responses that do not match the schema. Validate request parts with any [Standard Schema](https://standardschema.dev/) library, then read parsed values from `c.req.valid()`.

::: code-group

```ts [src/router.ts]
import { Hono } from 'hono';
import { createHonoOpenApiRouter } from 'openapi-ts-router';
import { zValidator } from 'validation-adapters/zod';
import * as z from 'zod';
import { paths } from './gen/v1'; // OpenAPI-generated types
import { PetSchema } from './schemas'; // Custom reusable schema for validation

export const router = new Hono();
export const openApiRouter = createHonoOpenApiRouter<paths>(router);

// GET /pet/{petId}
openApiRouter.get('/pet/{petId}', {
pathValidator: zValidator(
z.object({
petId: z.number() // Validate that petId is a number
})
),
import { Hono } from "hono";
import { createHonoOpenApiRouter } from "openapi-ts-router/hono";
import * as z from "zod";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const app = new Hono();
const openApiRouter = createHonoOpenApiRouter<paths>(app);

openApiRouter.get("/pet/{petId}", {
pathSchema: z.object({
petId: z.number(),
}),
handler: (c) => {
const { petId } = c.req.valid('param'); // Access validated params
return c.json({ name: 'Falko', photoUrls: [] });
}
const { petId } = c.req.valid("param");
return c.json({ name: `Pet ${petId}`, photoUrls: [] });
},
});

// POST /pet
openApiRouter.post('/pet', {
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
openApiRouter.post("/pet", {
bodySchema: z.object({
name: z.string(),
photoUrls: z.array(z.string()),
}),
handler: (c) => {
const { name, photoUrls } = c.req.valid('json'); // Access validated body data
return c.json({ name, photoUrls });
}
const { name, photoUrls } = c.req.valid("json");
return c.json({ name, photoUrls });
},
});

// TypeScript will error if route/method doesn't exist in OpenAPI spec
// or if response doesn't match defined schema
```

:::

[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/hono/petstore)

**Key benefits:**
- Full type safety for routes, methods, params, body and responses
- Runtime validation using Zod/Valibot
- Catches API spec mismatches at compile time
- Zero manual type definitions needed

## Express with [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router)

[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) provides full type-safety and runtime validation for your Express API routes by wrapping a [Express router](https://expressjs.com/en/5x/api.html#router):

::: tip Good to Know

While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both:
- Types verify your code matches the OpenAPI spec during development
- Validators ensure incoming requests match the spec at runtime

:::
[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) fits when an existing Express API should stay aligned with the OpenAPI document. It wraps an [Express router](https://expressjs.com/en/5x/api.html#router) and lets route registration use OpenAPI path strings such as `/pet/{petId}`. TypeScript rejects unknown paths, wrong methods, missing required schemas, and JSON success responses that do not match the schema. Validate request parts with any [Standard Schema](https://standardschema.dev/) library, then read parsed values from `req.valid`. Mount `express.json()` before this router when validating JSON bodies.

::: code-group

```ts [src/router.ts]
import { Router } from 'express';
import { createExpressOpenApiRouter } from 'openapi-ts-router';
import * as z from 'zod';
import { zValidator } from 'validation-adapters/zod';
import { paths } from './gen/v1'; // OpenAPI-generated types
import { PetSchema } from './schemas'; // Custom reusable schema for validation

export const router: Router = Router();
export const openApiRouter = createExpressOpenApiRouter<paths>(router);

// GET /pet/{petId}
openApiRouter.get('/pet/{petId}', {
pathValidator: zValidator(
z.object({
petId: z.number() // Validate that petId is a number
})
),
import { Router } from "express";
import { createExpressOpenApiRouter } from "openapi-ts-router/express";
import * as z from "zod";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript

const router = Router();
const openApiRouter = createExpressOpenApiRouter<paths>(router);

openApiRouter.get("/pet/{petId}", {
pathSchema: z.object({
petId: z.number(),
}),
handler: (req, res) => {
const { petId } = req.params; // Access validated params
res.send({ name: 'Falko', photoUrls: [] });
}
const { petId } = req.valid.path;
res.json({ name: `Pet ${petId}`, photoUrls: [] });
},
});

// POST /pet
openApiRouter.post('/pet', {
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
openApiRouter.post("/pet", {
bodySchema: z.object({
name: z.string(),
photoUrls: z.array(z.string()),
}),
handler: (req, res) => {
const { name, photoUrls } = req.body; // Access validated body data
res.send({ name, photoUrls });
}
const { name, photoUrls } = req.valid.body;
res.json({ name, photoUrls });
},
});

// TypeScript will error if route/method doesn't exist in OpenAPI spec
// or if response doesn't match defined schema
```

:::

[Full example](https://github.com/builder-group/community/tree/develop/examples/openapi-ts-router/express/petstore)

**Key benefits:**
- Full type safety for routes, methods, params, body and responses
- Runtime validation using Zod/Valibot
- Catches API spec mismatches at compile time
- Zero manual type definitions needed

## Mock-Service-Worker (MSW)

If you are using [Mock Service Worker (MSW)](https://mswjs.io) to define your API mocks, you can use a **small, automatically-typed wrapper** around MSW, which enables you to address conflicts in your API mocks easily when your OpenAPI specification changes. Ultimately, you can have the same level of confidence in your application's API client **and** API mocks.
Expand Down
3 changes: 2 additions & 1 deletion docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ type ErrorResponse =

From here, you can use these types for any of the following (but not limited to):

- Using an OpenAPI-supported fetch client (like [openapi-fetch](/openapi-fetch/))
- Calling APIs from generated `paths` (like [openapi-fetch](/openapi-fetch/) or [feature-fetch](/examples#data-fetching))
- Asserting types for other API requestBodies and responses
- Building core business logic based on API types
- Implementing server routes that compile against generated `paths` (like [openapi-ts-router](/examples#hono-with-openapi-ts-router))
- Validating mock test data is up-to-date with the current schema
- Packaging API types into any npm packages you publish (such as client SDKs, etc.)
Loading
Loading