Skip to content

Commit d286060

Browse files
committed
docs: link to example fetcher + inline example
1 parent 96b665a commit d286060

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ See [the online playground](https://typed-openapi-astahmer.vercel.app/)
1010

1111
## Features
1212

13-
- Headless API client, bring your own fetcher ! (fetch, axios, ky, etc...)
13+
- Headless API client, [bring your own fetcher](packages/typed-openapi/API_CLIENT_EXAMPLES.md#basic-api-client-api-client-examplets) (fetch, axios, ky, etc...) !
1414
- Generates a fully typesafe API client with just types by default (instant suggestions)
1515
- **Type-safe error handling** with discriminated unions and configurable success status codes
1616
- **TanStack Query integration** with `withResponse` and `selectFn` options for advanced error handling

packages/typed-openapi/API_CLIENT_EXAMPLES.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,94 @@ A simple, dependency-free client that handles:
1111
- Custom headers
1212
- Basic error handling
1313

14+
```typescript
15+
/**
16+
* Generic API Client for typed-openapi generated code
17+
*
18+
* This is a simple, production-ready wrapper that you can copy and customize.
19+
* It handles:
20+
* - Path parameter replacement
21+
* - Query parameter serialization
22+
* - JSON request/response handling
23+
* - Basic error handling
24+
*
25+
* Usage:
26+
* 1. Replace './generated/api' with your actual generated file path
27+
* 2. Set your API_BASE_URL
28+
* 3. Customize error handling and headers as needed
29+
*/
30+
31+
import { type Fetcher, createApiClient } from "../tmp/generated-client.ts";
32+
33+
// Basic configuration
34+
const API_BASE_URL = process.env["API_BASE_URL"] || "https://api.example.com";
35+
36+
/**
37+
* Simple fetcher implementation without external dependencies
38+
*/
39+
const fetcher: Fetcher = async (method, apiUrl, params) => {
40+
const headers = new Headers();
41+
42+
// Replace path parameters (supports both {param} and :param formats)
43+
const actualUrl = replacePathParams(apiUrl, (params?.path ?? {}) as Record<string, string>);
44+
const url = new URL(actualUrl);
45+
46+
// Handle query parameters
47+
if (params?.query) {
48+
const searchParams = new URLSearchParams();
49+
Object.entries(params.query).forEach(([key, value]) => {
50+
if (value != null) {
51+
// Skip null/undefined values
52+
if (Array.isArray(value)) {
53+
value.forEach((val) => val != null && searchParams.append(key, String(val)));
54+
} else {
55+
searchParams.append(key, String(value));
56+
}
57+
}
58+
});
59+
url.search = searchParams.toString();
60+
}
61+
62+
// Handle request body for mutation methods
63+
const body = ["post", "put", "patch", "delete"].includes(method.toLowerCase())
64+
? JSON.stringify(params?.body)
65+
: undefined;
66+
67+
if (body) {
68+
headers.set("Content-Type", "application/json");
69+
}
70+
71+
// Add custom headers
72+
if (params?.header) {
73+
Object.entries(params.header).forEach(([key, value]) => {
74+
if (value != null) {
75+
headers.set(key, String(value));
76+
}
77+
});
78+
}
79+
80+
const response = await fetch(url, {
81+
method: method.toUpperCase(),
82+
...(body && { body }),
83+
headers,
84+
});
85+
86+
return response;
87+
};
88+
89+
/**
90+
* Replace path parameters in URL
91+
* Supports both OpenAPI format {param} and Express format :param
92+
*/
93+
function replacePathParams(url: string, params: Record<string, string>): string {
94+
return url
95+
.replace(/{(\w+)}/g, (_, key: string) => params[key] || `{${key}}`)
96+
.replace(/:([a-zA-Z0-9_]+)/g, (_, key: string) => params[key] || `:${key}`);
97+
}
98+
99+
export const api = createApiClient(fetcher, API_BASE_URL);
100+
```
101+
14102
### Setup
15103

16104
1. Copy the file to your project

0 commit comments

Comments
 (0)