Skip to content

Commit c009da6

Browse files
committed
#main update feature-fetch & openapi-ts-router examples
1 parent 4a1338c commit c009da6

10 files changed

Lines changed: 160 additions & 244 deletions

File tree

docs/examples.md

Lines changed: 73 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -78,66 +78,53 @@ try {
7878
</details>
7979

8080
<details>
81-
<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>
81+
<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>
82+
83+
`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`. Calls return `[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.
8284

8385
::: code-group
8486

8587
```ts [test/my-project.ts]
86-
import { createOpenApiFetchClient } from 'feature-fetch';
87-
import type { paths } from './my-openapi-3-schema'; // generated by openapi-typescript
88+
import {
89+
createOpenApiFetchClient,
90+
hasStatusCode,
91+
retryFeature,
92+
} from "feature-fetch";
93+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
8894

89-
// Create the OpenAPI fetch client
90-
const fetchClient = createOpenApiFetchClient<paths>({
91-
prefixUrl: 'https://myapi.dev/v1'
92-
});
95+
const api = createOpenApiFetchClient<paths>({
96+
baseUrl: "https://myapi.dev/v1",
97+
}).with(retryFeature({ maxRetries: 3 }));
9398

94-
// Send a GET request
95-
const response = await fetchClient.get('/blogposts/{post_id}', {
99+
const [isPostOk, postErr, post] = await api.get("/blogposts/{post_id}", {
96100
pathParams: {
97-
post_id: '123',
101+
post_id: "123",
98102
},
99103
});
100104

101-
// Handle the response (Approach 1: Standard if-else)
102-
if (response.isOk()) {
103-
const data = response.value.data;
104-
console.log(data); // Handle successful response
105+
if (isPostOk) {
106+
console.log(post); // typed from the 2XX response schema
107+
} else if (hasStatusCode(postErr, 404)) {
108+
console.error("Post not found");
105109
} else {
106-
const error = response.error;
107-
if (error instanceof NetworkError) {
108-
console.error('Network error:', error.message);
109-
} else if (error instanceof RequestError) {
110-
console.error('Request error:', error.message, 'Status:', error.status);
111-
} else {
112-
console.error('Service error:', error.message);
113-
}
110+
console.error(postErr.message); // NetworkError | HttpError | FetchError
114111
}
115112

116-
// Send a PUT request
117-
const putResponse = await fetchClient.put('/blogposts', {
113+
const [isUpdateOk, updateErr] = await api.put("/blogposts", {
118114
body: {
119-
title: 'My New Post',
115+
title: "My New Post", // checked against the request body schema
120116
},
121117
});
122118

123-
// Handle the response (Approach 2: Try-catch)
124-
try {
125-
const putData = putResponse.unwrap().data;
126-
console.log(putData); // Handle the successful response
127-
} catch (error) {
128-
// Handle the error
129-
if (error instanceof NetworkError) {
130-
console.error('Network error:', error.message);
131-
} else if (error instanceof RequestError) {
132-
console.error('Request error:', error.message, 'Status:', error.status);
133-
} else {
134-
console.error('Service error:', error.message);
135-
}
119+
if (!isUpdateOk) {
120+
console.error(updateErr.message);
136121
}
137122
```
138123

139124
:::
140125

126+
[Full example](https://github.com/builder-group/community/tree/develop/examples/feature-fetch/vanilla/basic)
127+
141128
</details>
142129

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

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

262-
[`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):
263-
264-
::: tip Good to Know
265-
266-
While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both:
267-
- Types verify your code matches the OpenAPI spec during development
268-
- Validators ensure incoming requests match the spec at runtime
269-
270-
:::
249+
[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) fits when the OpenAPI document should constrain the server code that implements it. It wraps a [Hono router](https://hono.dev/docs/api/routing), keeps OpenAPI path strings such as `/pet/{petId}` in route registration, and 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()`.
271250

272251
::: code-group
273252

274253
```ts [src/router.ts]
275-
import { Hono } from 'hono';
276-
import { createHonoOpenApiRouter } from 'openapi-ts-router';
277-
import { zValidator } from 'validation-adapters/zod';
278-
import * as z from 'zod';
279-
import { paths } from './gen/v1'; // OpenAPI-generated types
280-
import { PetSchema } from './schemas'; // Custom reusable schema for validation
281-
282-
export const router = new Hono();
283-
export const openApiRouter = createHonoOpenApiRouter<paths>(router);
284-
285-
// GET /pet/{petId}
286-
openApiRouter.get('/pet/{petId}', {
287-
pathValidator: zValidator(
288-
z.object({
289-
petId: z.number() // Validate that petId is a number
290-
})
291-
),
254+
import { Hono } from "hono";
255+
import { createHonoOpenApiRouter } from "openapi-ts-router/hono";
256+
import * as z from "zod";
257+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
258+
259+
const app = new Hono();
260+
const openApiRouter = createHonoOpenApiRouter<paths>(app);
261+
262+
openApiRouter.get("/pet/{petId}", {
263+
pathSchema: z.object({
264+
petId: z.number(),
265+
}),
292266
handler: (c) => {
293-
const { petId } = c.req.valid('param'); // Access validated params
294-
return c.json({ name: 'Falko', photoUrls: [] });
295-
}
267+
const { petId } = c.req.valid("param");
268+
return c.json({ name: `Pet ${petId}`, photoUrls: [] });
269+
},
296270
});
297271

298-
// POST /pet
299-
openApiRouter.post('/pet', {
300-
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
272+
openApiRouter.post("/pet", {
273+
bodySchema: z.object({
274+
name: z.string(),
275+
photoUrls: z.array(z.string()),
276+
}),
301277
handler: (c) => {
302-
const { name, photoUrls } = c.req.valid('json'); // Access validated body data
303-
return c.json({ name, photoUrls });
304-
}
278+
const { name, photoUrls } = c.req.valid("json");
279+
return c.json({ name, photoUrls });
280+
},
305281
});
306-
307-
// TypeScript will error if route/method doesn't exist in OpenAPI spec
308-
// or if response doesn't match defined schema
309282
```
310283

311284
:::
312285

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

315-
**Key benefits:**
316-
- Full type safety for routes, methods, params, body and responses
317-
- Runtime validation using Zod/Valibot
318-
- Catches API spec mismatches at compile time
319-
- Zero manual type definitions needed
320-
321288
## Express with [`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router)
322289

323-
[`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):
324-
325-
::: tip Good to Know
326-
327-
While TypeScript ensures compile-time type safety, runtime validation is equally important. `openapi-ts-router` integrates with Zod/Valibot to provide both:
328-
- Types verify your code matches the OpenAPI spec during development
329-
- Validators ensure incoming requests match the spec at runtime
330-
331-
:::
290+
[`openapi-ts-router`](https://github.com/builder-group/community/tree/develop/packages/openapi-ts-router) fits when the OpenAPI document should constrain an existing Express API implementation. It wraps an [Express router](https://expressjs.com/en/5x/api.html#router), keeps OpenAPI path strings such as `/pet/{petId}` in route registration, and 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.
332291

333292
::: code-group
334293

335294
```ts [src/router.ts]
336-
import { Router } from 'express';
337-
import { createExpressOpenApiRouter } from 'openapi-ts-router';
338-
import * as z from 'zod';
339-
import { zValidator } from 'validation-adapters/zod';
340-
import { paths } from './gen/v1'; // OpenAPI-generated types
341-
import { PetSchema } from './schemas'; // Custom reusable schema for validation
342-
343-
export const router: Router = Router();
344-
export const openApiRouter = createExpressOpenApiRouter<paths>(router);
345-
346-
// GET /pet/{petId}
347-
openApiRouter.get('/pet/{petId}', {
348-
pathValidator: zValidator(
349-
z.object({
350-
petId: z.number() // Validate that petId is a number
351-
})
352-
),
295+
import { Router } from "express";
296+
import { createExpressOpenApiRouter } from "openapi-ts-router/express";
297+
import * as z from "zod";
298+
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typescript
299+
300+
const router = Router();
301+
const openApiRouter = createExpressOpenApiRouter<paths>(router);
302+
303+
openApiRouter.get("/pet/{petId}", {
304+
pathSchema: z.object({
305+
petId: z.number(),
306+
}),
353307
handler: (req, res) => {
354-
const { petId } = req.params; // Access validated params
355-
res.send({ name: 'Falko', photoUrls: [] });
356-
}
308+
const { petId } = req.valid.path;
309+
res.json({ name: `Pet ${petId}`, photoUrls: [] });
310+
},
357311
});
358312

359-
// POST /pet
360-
openApiRouter.post('/pet', {
361-
bodyValidator: zValidator(PetSchema), // Validate request body using PetSchema
313+
openApiRouter.post("/pet", {
314+
bodySchema: z.object({
315+
name: z.string(),
316+
photoUrls: z.array(z.string()),
317+
}),
362318
handler: (req, res) => {
363-
const { name, photoUrls } = req.body; // Access validated body data
364-
res.send({ name, photoUrls });
365-
}
319+
const { name, photoUrls } = req.valid.body;
320+
res.json({ name, photoUrls });
321+
},
366322
});
367-
368-
// TypeScript will error if route/method doesn't exist in OpenAPI spec
369-
// or if response doesn't match defined schema
370323
```
371324

372325
:::
373326

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

376-
**Key benefits:**
377-
- Full type safety for routes, methods, params, body and responses
378-
- Runtime validation using Zod/Valibot
379-
- Catches API spec mismatches at compile time
380-
- Zero manual type definitions needed
381-
382329
## Mock-Service-Worker (MSW)
383330

384331
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.

docs/introduction.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ type ErrorResponse =
105105

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

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

0 commit comments

Comments
 (0)