Skip to content

Commit aba7866

Browse files
committed
chore: add examples
1 parent 6c37efa commit aba7866

File tree

4 files changed

+465
-11
lines changed

4 files changed

+465
-11
lines changed

README.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,26 @@ npx typed-openapi -h
3737
```
3838

3939
```sh
40-
typed-openapi/0.1.3
41-
42-
Usage: $ typed-openapi <input>
43-
44-
Commands: <input> Generate
45-
46-
For more info, run any command with the `--help` flag: $ typed-openapi --help
47-
48-
Options: -o, --output <path> Output path for the api client ts file (defaults to `<input>.<runtime>.ts`) -r, --runtime
49-
<name> Runtime to use for validation; defaults to `none`; available: 'none' | 'arktype' | 'io-ts' | 'typebox' |
50-
'valibot' | 'yup' | 'zod' (default: none) -h, --help Display this message -v, --version Display version number
40+
typed-openapi/1.5.0
41+
42+
Usage:
43+
$ typed-openapi <input>
44+
45+
Commands:
46+
<input> Generate
47+
48+
For more info, run any command with the `--help` flag:
49+
$ typed-openapi --help
50+
51+
Options:
52+
-o, --output <path> Output path for the api client ts file (defaults to `<input>.<runtime>.ts`)
53+
-r, --runtime <n> Runtime to use for validation; defaults to `none`; available: Type<"arktype" | "io-ts" | "none" | "typebox" | "valibot" | "yup" | "zod"> (default: none)
54+
--schemas-only Only generate schemas, skipping client generation (defaults to false) (default: false)
55+
--include-client Include API client types and implementation (defaults to true) (default: true)
56+
--success-status-codes <codes> Comma-separated list of success status codes (defaults to 2xx and 3xx ranges)
57+
--tanstack [name] Generate tanstack client, defaults to false, can optionally specify a name for the generated file
58+
-h, --help Display this message
59+
-v, --version Display version number
5160
```
5261

5362
## Non-goals
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# API Client Examples
2+
3+
These are production-ready API client wrappers for your generated typed-openapi code. Copy the one that fits your needs and customize it.
4+
5+
## Basic API Client (`api-client-example.ts`)
6+
7+
A simple, dependency-free client that handles:
8+
- Path parameter replacement (`{id}` and `:id` formats)
9+
- Query parameter serialization (including arrays)
10+
- JSON request/response handling
11+
- Custom headers
12+
- Basic error handling
13+
14+
### Setup
15+
16+
1. Copy the file to your project
17+
2. Update the import path to your generated API file:
18+
```typescript
19+
import { type EndpointParameters, type Fetcher, createApiClient } from './generated/api';
20+
```
21+
3. Set your API base URL:
22+
```typescript
23+
const API_BASE_URL = process.env['API_BASE_URL'] || 'https://your-api.com';
24+
```
25+
4. Uncomment the client creation:
26+
```typescript
27+
export const api = createApiClient(fetcher, API_BASE_URL);
28+
```
29+
30+
### Usage
31+
32+
```typescript
33+
// GET request with query params
34+
const users = await api.get('/users', {
35+
query: { page: 1, limit: 10, tags: ['admin', 'user'] }
36+
});
37+
38+
// POST request with body
39+
const newUser = await api.post('/users', {
40+
body: { name: 'John', email: 'john@example.com' }
41+
});
42+
43+
// With path parameters
44+
const user = await api.get('/users/{id}', {
45+
path: { id: '123' }
46+
});
47+
48+
// With custom headers
49+
const result = await api.get('/protected', {
50+
header: { Authorization: 'Bearer your-token' }
51+
});
52+
```
53+
54+
## Validating API Client (`api-client-with-validation.ts`)
55+
56+
Extends the basic client with schema validation for:
57+
- Request body validation before sending
58+
- Response validation after receiving
59+
- Type-safe validation error handling
60+
61+
### Setup
62+
63+
1. Follow the basic client setup steps above
64+
2. Import your validation library and schemas:
65+
```typescript
66+
// For Zod
67+
import { z } from 'zod';
68+
import { EndpointByMethod } from './generated/api';
69+
70+
// For Yup
71+
import * as yup from 'yup';
72+
import { EndpointByMethod } from './generated/api';
73+
```
74+
3. Implement the validation logic in the marked TODO sections
75+
4. Configure validation settings:
76+
```typescript
77+
const VALIDATE_REQUESTS = true; // Validate request bodies
78+
const VALIDATE_RESPONSES = true; // Validate response data
79+
```
80+
81+
### Validation Implementation Example (Zod)
82+
83+
```typescript
84+
// Request validation
85+
if (VALIDATE_REQUESTS && params?.body) {
86+
const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod];
87+
const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint];
88+
if (pathSchema?.body) {
89+
pathSchema.body.parse(params.body); // Throws if invalid
90+
}
91+
}
92+
93+
// Response validation
94+
const responseData = await responseClone.json();
95+
const endpoint = EndpointByMethod[method as keyof typeof EndpointByMethod];
96+
const pathSchema = endpoint?.[actualUrl as keyof typeof endpoint];
97+
const statusSchema = pathSchema?.responses?.[response.status];
98+
if (statusSchema) {
99+
statusSchema.parse(responseData); // Throws if invalid
100+
}
101+
```
102+
103+
### Error Handling
104+
105+
```typescript
106+
try {
107+
const result = await api.post('/users', {
108+
body: { name: 'John', email: 'invalid-email' }
109+
});
110+
} catch (error) {
111+
if (error instanceof ValidationError) {
112+
if (error.type === 'request') {
113+
console.error('Invalid request data:', error.validationErrors);
114+
} else {
115+
console.error('Invalid response data:', error.validationErrors);
116+
}
117+
} else {
118+
console.error('Network or HTTP error:', error);
119+
}
120+
}
121+
```
122+
123+
## Customization Ideas
124+
125+
- **Authentication**: Add token handling, refresh logic, or auth headers
126+
- **Retries**: Implement retry logic for failed requests
127+
- **Caching**: Add response caching with TTL
128+
- **Logging**: Add request/response logging for debugging
129+
- **Rate limiting**: Implement client-side rate limiting
130+
- **Metrics**: Add performance monitoring and error tracking
131+
- **Base URL per environment**: Different URLs for dev/staging/prod
132+
133+
## Error Handling Enhancement
134+
135+
You can enhance error handling by creating custom error classes:
136+
137+
```typescript
138+
class ApiError extends Error {
139+
constructor(
140+
public readonly status: number,
141+
public readonly statusText: string,
142+
public readonly response: Response
143+
) {
144+
super(`HTTP ${status}: ${statusText}`);
145+
this.name = 'ApiError';
146+
}
147+
}
148+
149+
// In your fetcher:
150+
if (!response.ok) {
151+
throw new ApiError(response.status, response.statusText, response);
152+
}
153+
```
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Generic API Client for typed-openapi generated code
3+
*
4+
* This is a simple, production-ready wrapper that you can copy and customize.
5+
* It handles:
6+
* - Path parameter replacement
7+
* - Query parameter serialization
8+
* - JSON request/response handling
9+
* - Basic error handling
10+
*
11+
* Usage:
12+
* 1. Replace './generated/api' with your actual generated file path
13+
* 2. Set your API_BASE_URL
14+
* 3. Customize error handling and headers as needed
15+
*/
16+
17+
// TODO: Replace with your generated API client imports
18+
// import { type EndpointParameters, type Fetcher, createApiClient } from './generated/api';
19+
20+
// Basic configuration
21+
const API_BASE_URL = process.env["API_BASE_URL"] || "https://api.example.com";
22+
23+
// Generic types for when you haven't imported the generated types yet
24+
type EndpointParameters = {
25+
body?: unknown;
26+
query?: Record<string, unknown>;
27+
header?: Record<string, unknown>;
28+
path?: Record<string, unknown>;
29+
};
30+
31+
type Fetcher = (method: string, url: string, params?: EndpointParameters) => Promise<Response>;
32+
33+
/**
34+
* Simple fetcher implementation without external dependencies
35+
*/
36+
const fetcher: Fetcher = async (method, apiUrl, params) => {
37+
const headers = new Headers();
38+
39+
// Replace path parameters (supports both {param} and :param formats)
40+
const actualUrl = replacePathParams(apiUrl, (params?.path ?? {}) as Record<string, string>);
41+
const url = new URL(actualUrl);
42+
43+
// Handle query parameters
44+
if (params?.query) {
45+
const searchParams = new URLSearchParams();
46+
Object.entries(params.query).forEach(([key, value]) => {
47+
if (value != null) {
48+
// Skip null/undefined values
49+
if (Array.isArray(value)) {
50+
value.forEach((val) => val != null && searchParams.append(key, String(val)));
51+
} else {
52+
searchParams.append(key, String(value));
53+
}
54+
}
55+
});
56+
url.search = searchParams.toString();
57+
}
58+
59+
// Handle request body for mutation methods
60+
const body = ["post", "put", "patch", "delete"].includes(method.toLowerCase())
61+
? JSON.stringify(params?.body)
62+
: undefined;
63+
64+
if (body) {
65+
headers.set("Content-Type", "application/json");
66+
}
67+
68+
// Add custom headers
69+
if (params?.header) {
70+
Object.entries(params.header).forEach(([key, value]) => {
71+
if (value != null) {
72+
headers.set(key, String(value));
73+
}
74+
});
75+
}
76+
77+
const response = await fetch(url, {
78+
method: method.toUpperCase(),
79+
...(body && { body }),
80+
headers,
81+
});
82+
83+
if (!response.ok) {
84+
// You can customize error handling here
85+
const error = new Error(`HTTP ${response.status}: ${response.statusText}`);
86+
(error as any).response = response;
87+
(error as any).status = response.status;
88+
throw error;
89+
}
90+
91+
return response;
92+
};
93+
94+
/**
95+
* Replace path parameters in URL
96+
* Supports both OpenAPI format {param} and Express format :param
97+
*/
98+
function replacePathParams(url: string, params: Record<string, string>): string {
99+
return url
100+
.replace(/{(\w+)}/g, (_, key: string) => params[key] || `{${key}}`)
101+
.replace(/:([a-zA-Z0-9_]+)/g, (_, key: string) => params[key] || `:${key}`);
102+
}
103+
104+
// TODO: Uncomment and replace with your generated createApiClient
105+
// export const api = createApiClient(fetcher, API_BASE_URL);
106+
107+
// Example of how to create the client once you have the generated code:
108+
// export const api = createApiClient(fetcher, API_BASE_URL);

0 commit comments

Comments
 (0)