codegen-openapi
v1.2.1
Published
Zero-dependency CLI that generates Next.js route handlers, typed services, and React Query hooks from any OpenAPI/Swagger spec
Maintainers
Readme
codegen-openapi
A lightning-fast, zero-dependency CLI that generates Next.js API handlers (App Router or Pages Router), fully typed Frontend Services, and React hooks directly from any OpenAPI / Swagger specification.
Works with Next.js (App Router and Pages Router) and standalone React projects.
Stop writing boilerplate. Connect your APIs in seconds.
Table of Contents
- Features
- Installation
- Quick Start
- Commands
- Configuration Reference
- What Gets Generated
- Contributing
- License
Features
- Zero dependencies — powered by raw Node.js builtins, no bloated toolchains
- Next.js App Router — generates modern
route.tshandlers with asynccontext.params(Next.js 15+ ready) - Next.js Pages Router — generates
export default function handlerfiles for legacy Next.js projects (pre-13) - React support — generates typed services and hooks for standalone React projects
- Two hook strategies — choose
react-query(useQuery/useMutation with caching) orfetch(useState/useEffect with zero extra deps) - End-to-end TypeScript — types derived directly from your OpenAPI schemas, including
allOf,oneOf,anyOf, and nullable support - Dynamic route conflict resolution — automatically normalizes clashing path params (e.g.
{id}vs{userId}at the same folder level) so Next.js never throws the "different slug names" error - Authentication built-in — automatic JWT cookie propagation between browser, route handler, and backend API (optional)
- Interactive setup —
openapi-gen runguides you through the full configuration in under a minute - Diff before you generate —
openapi-gen diffshows exactly what changed in your spec before writing any files - Config validation — clear, actionable errors pointing to the exact field before any file is touched
- Multiple APIs — configure several OpenAPI specs in a single config file, each generating independently
Installation
Install as a dev dependency in your project:
# npm
npm install --save-dev codegen-openapi
# pnpm
pnpm add -D codegen-openapi
# yarn
yarn add -D codegen-openapi
# bun
bun add -d codegen-openapiOr run directly without installing:
npx openapi-gen runReact projects — optional peer dependency
If you use framework: 'react' with hooksMode: 'react-query' (the default), the generated hooks require @tanstack/react-query installed in your project:
npm install @tanstack/react-queryIf you use hooksMode: 'fetch' instead, no extra dependency is needed — hooks use useState and useEffect from React itself.
The codegen itself has zero dependencies regardless of which mode you choose.
Quick Start
New project — use the interactive wizard:
npx openapi-gen runGuides you through setup, saves your config, generates everything, and asks if you want to connect a second API right away.
Connect a second (or third) API to the same project:
npx openapi-gen addAsks only name + spec URL — inherits auth, framework, hooks mode, and shared settings from your existing config.
Re-generate after your backend changes:
npx openapi-gen generateCheck what changed in your spec before re-generating:
npx openapi-gen diffTypical multi-service setup:
npx openapi-gen run # → configure core API (main backend)
npx openapi-gen add # → add payments service
npx openapi-gen add # → add notifications service
npx openapi-gen generate # → generates all 3 at onceCommands
run
npx openapi-gen run
npx openapi-gen run --config ./configs/my-api.mjsRecommended for first-time setup. Launches an interactive wizard that guides you through every configuration option step by step. No need to read docs first — everything is explained inline with examples.
What it asks:
- Framework —
nextjs,nextjs-pages, orreact- If
react: Hooks library —react-queryorfetch(asked immediately after)
- If
- API name — label for CLI output
- OpenAPI spec — URL or local file path of your first API (required)
- Path prefix to strip — prevents double-nesting like
/api/api/users - Backend URL — env variable name and fallback (Next.js and Pages Router only)
- JWT cookie name — leave blank to skip auth entirely
- Generate now? — run
generateimmediately after saving
After saving (and optionally generating), the wizard asks:
Do you have another API to connect?
Example: a payments service, a notifications API, a separate microservice...
Add another API to this config? (y/N):Say y to chain directly into openapi-gen add for the next API. Repeat as many times as needed.
If a config file already exists, it asks whether to overwrite it before proceeding.
Options:
| Flag | Description | Default |
|------|-------------|---------|
| --config <path> | Where to write the config file | openapi-gen.config.mjs |
add
npx openapi-gen add
npx openapi-gen add --config ./configs/my-api.mjsAppends a new API entry to an existing config file. This is the fastest way to connect a second (or third) backend service — it inherits your existing framework, auth, hooks mode, and shared settings so you only answer what's different.
What it asks (Next.js: 6 steps, React: 5 steps):
- API name — label and default folder name for this API
- OpenAPI spec — URL or local file path for the new service
- Authentication — keep the same JWT cookie, use a different one, or disable auth for this API
- Output directories — routes, services (and hooks if React) with smart defaults
- If
react: Hooks library — inherit from base or override (react-query/fetch)
- If
- Backend URL (Next.js only) — env variable and fallback for this service
- Generate now? — re-run
generatefor all APIs immediately
What is inherited automatically (no questions asked):
| Field | Inherited from |
|-------|---------------|
| framework | first entry |
| cookieName | first entry (overridable in step 3) |
| hooksMode | first entry (overridable in step 4 for React) |
| stripPathPrefix | first entry |
| apiClient | set to false — already generated |
| fetchBackend | set to false — already generated |
Example session (Next.js):
openapi-gen add
Adding to openapi-gen.config.mjs (1 API configured)
Inheriting: framework=nextjs, cookieName=accessToken, stripPathPrefix=/api
Step 1 — API name
Name (e.g. payments): payments
Step 2 — OpenAPI spec
Spec URL or path (required): https://payments.example.com/api-json
Step 3 — Authentication
Base API uses cookieName='accessToken'
Enter to keep the same | type a new name to override | - to disable auth
Cookie name (accessToken):
Step 4 — Output directories
Routes output (src/app/api): src/app/api/payments
Services output (src/services/payments):
Step 5 — Backend URL
Env variable (PAYMENTS_API_URL):
Fallback URL (leave blank):
Step 6 — Generate now? (Y/n): y
✓ Config updated — 2 APIs configuredRun
openapi-gen addas many times as needed. Each new entry is appended to the array andopenapi-gen generateprocesses all of them in one shot.
Options:
| Flag | Description | Default |
|------|-------------|---------|
| --config <path> | Path to config file | openapi-gen.config.mjs |
generate
npx openapi-gen generate
npx openapi-gen generate --config ./configs/my-api.mjsReads your config and generates all output files. The exact steps depend on framework:
App Router (framework: 'nextjs'):
- Validates config — stops with clear errors before touching any file
- Generates
apiClient.ts(unlessapiClient: false) - Generates
fetchBackend.ts(unlessfetchBackend: false) - Fetches the OpenAPI spec
- Generates one
route.tsper API path insrc/app/api/ - Generates one service directory per OpenAPI tag
Pages Router (framework: 'nextjs-pages'):
- Validates config
- Generates
apiClient.ts(unlessapiClient: false) - Generates
fetchBackend.ts(unlessfetchBackend: false) - Fetches the OpenAPI spec
- Generates one handler file per API path in
pages/api/(e.g.pages/api/users/[id].ts) - Generates one service directory per OpenAPI tag
React (framework: 'react'):
- Validates config
- Generates
apiClient.ts(unlessapiClient: false) - Fetches the OpenAPI spec
- Generates one service directory per OpenAPI tag
- Generates one hooks file per OpenAPI tag (
hooksOut/{slug}/index.ts) — style depends onhooksMode
Options:
| Flag | Description | Default |
|------|-------------|---------|
| --config <path> | Path to config file | openapi-gen.config.mjs |
In package.json scripts:
{
"scripts": {
"codegen": "openapi-gen generate"
}
}diff
npx openapi-gen diff
npx openapi-gen diff --config ./configs/my-api.mjsFetches the latest spec and compares what would be generated against what already exists on disk — without writing any files.
Use this after your backend team deploys API changes to understand what needs to be re-generated:
Next.js output:
openapi-gen diff — spec vs disk
[my-api] (nextjs)
→ fetching spec: https://api.example.com/api-json
+ 2 new route(s) not yet generated:
+ src/app/api/payments/[id]/route.ts
+ src/app/api/webhooks/route.ts
- 1 route file(s) no longer in spec:
- src/app/api/legacy/users/route.ts
5 route(s) unchanged
✓ Services up to date — 4 service(s)
Run npx openapi-gen generate to apply changes.React output:
[my-api] (react)
→ fetching spec: https://api.example.com/api-json
✓ Services up to date — 4 service(s)
+ 1 new hook file(s) not yet generated:
+ src/hooks/payments/index.ts
3 hook file(s) unchangedOptions:
| Flag | Description | Default |
|------|-------------|---------|
| --config <path> | Path to config file | openapi-gen.config.mjs |
init
npx openapi-gen init
npx openapi-gen init --config ./configs/my-api.mjsWrites a blank starter openapi-gen.config.mjs with all fields documented inline as comments. Does nothing if the file already exists.
For first-time setup, prefer
run— it fills in the values for you based on your answers.
Options:
| Flag | Description | Default |
|------|-------------|---------|
| --config <path> | Output path for the config file | openapi-gen.config.mjs |
Configuration Reference
Your config file (openapi-gen.config.mjs) exports an array of config objects:
/** @type {import('codegen-openapi').CodegenConfig[]} */
export default [
{
name: 'my-api',
framework: 'nextjs',
spec: 'https://api.example.com/api-json',
routesOut: 'src/app/api',
servicesOut: 'src/services',
apiEnvVar: 'API_URL',
apiFallback: 'https://api.example.com',
stripPathPrefix: '/api',
cookieName: 'accessToken',
apiClient: {
outputPath: 'src/lib/apiClient.ts',
deviceTracking: false,
unauthorizedRedirect: '/auth',
},
fetchBackend: {
outputPath: 'src/lib/fetchBackend.ts',
timeout: 15000,
},
},
];framework
| | |
|---|---|
| Type | 'nextjs' \| 'nextjs-pages' \| 'react' |
| Required | No |
| Default | 'nextjs' |
Controls which files are generated:
| | nextjs | nextjs-pages | react |
|---|---|---|---|
| apiClient.ts | ✅ | ✅ | ✅ |
| fetchBackend.ts | ✅ | ✅ | — |
| route.ts per path (App Router) | ✅ | — | — |
| handler.ts per path (Pages Router) | — | ✅ | — |
| Services per tag | ✅ | ✅ | ✅ |
| Hooks per tag | — | — | ✅ (style set by hooksMode) |
framework: 'nextjs' // App Router — route.ts in src/app/api/
framework: 'nextjs-pages' // Pages Router — handler files in pages/api/
framework: 'react' // React only — services + hooks, no server proxyname
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "default" |
A short label shown in CLI output to identify this entry. Useful when managing multiple APIs.
name: 'payments'spec
| | |
|---|---|
| Type | string |
| Required | Yes |
URL or local file path to your OpenAPI / Swagger JSON spec.
spec: 'https://api.example.com/api-json' // remote URL
spec: './openapi.json' // local file (relative to cwd)YAML specs are not currently supported. Export your spec as JSON before use.
routesOut
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "src/app/api" (App Router) / "pages/api" (Pages Router) |
| Applies to | nextjs and nextjs-pages |
Directory where generated route/handler files are written. Created automatically if it does not exist.
- App Router (
nextjs): files are written as{routesOut}/{path}/route.ts - Pages Router (
nextjs-pages): files are written as{routesOut}/{path}.ts
routesOut: 'src/app/api' // App Router default
routesOut: 'pages/api' // Pages Router defaultService URL derivation: the generated services automatically use routesOut to build the correct URL when calling the Next.js proxy routes. You do not need to configure anything extra — if your routes live at src/app/api/core, the services will call /api/core/....
| routesOut | Generated service URL prefix |
|---|---|
| src/app/api | /api |
| src/app/api/core | /api/core |
| src/app/api/payments | /api/payments |
| pages/api | /api |
| pages/api/payments | /api/payments |
When connecting multiple APIs, use different
routesOutsubdirectories to avoid files from one API overwriting another:// core API — routes at /api/core/..., services call /api/core/... routesOut: 'src/app/api/core' // payments API — routes at /api/payments/..., services call /api/payments/... routesOut: 'src/app/api/payments'
servicesOut
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "src/services" |
Directory where typed service modules are written. Each OpenAPI tag becomes a subdirectory. Created automatically if it does not exist.
servicesOut: 'src/services'hooksOut
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "src/hooks" |
| Applies to | react only |
Directory where generated hook files are written. Each OpenAPI tag becomes a subdirectory containing an index.ts. Created automatically if it does not exist.
hooksOut: 'src/hooks'hooksMode
| | |
|---|---|
| Type | 'react-query' \| 'fetch' |
| Required | No |
| Default | 'react-query' |
| Applies to | react only |
Controls the style of hooks generated in hooksOut. Choose based on whether you want caching and deduplication or a zero-dependency solution.
| | react-query | fetch |
|---|---|---|
| GET hooks | useQuery | useState + useEffect |
| Mutation hooks | useMutation + invalidateQueries | mutate async function |
| Caching & deduplication | ✅ automatic | ❌ manual |
| Background refetch | ✅ | ❌ |
| Extra dependency | @tanstack/react-query | none |
hooksMode: 'react-query' // default — recommended for most apps
hooksMode: 'fetch' // zero extra deps, plain ReactapiEnvVar
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "API_URL" |
| Applies to | nextjs and nextjs-pages |
Name of the process.env variable that holds the backend base URL. Generated route/handler files read this at runtime.
apiEnvVar: 'CORE_API_URL'The generated handler will contain:
const API_URL = process.env.CORE_API_URL || '<apiFallback>';apiFallback
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "" |
| Applies to | nextjs and nextjs-pages |
Hardcoded URL used when apiEnvVar is not set in the environment. Useful for local development.
apiFallback: 'https://api.example.com'stripPathPrefix
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "/api" |
Removes this prefix from all OpenAPI paths before creating route files or service method URLs. Prevents double-nesting like src/app/api/api/users/route.ts.
stripPathPrefix: '/api'
// /api/users/{id} → /users/{id} → src/app/api/users/[id]/route.tsSet to "" (empty string) to disable stripping entirely.
cookieName
| | |
|---|---|
| Type | string |
| Required | No |
| Default | undefined (auth disabled) |
Name of the HTTP-only cookie that stores the JWT token.
- In
apiClient.ts— reads this cookie in the browser and attaches it asAuthorization: Bearer <token>on every request - In
fetchBackend.ts— reads this cookie server-side vianext/headersand propagates it to backend requests
cookieName: 'accessToken'Omit or leave blank in the wizard to disable automatic auth propagation entirely.
apiClientPath
| | |
|---|---|
| Type | string |
| Required | No |
| Default | "@/lib/apiClient" |
Import path that generated service files use to import the apiClient instance.
apiClientPath: '@/lib/apiClient'apiClient
| | |
|---|---|
| Type | false \| object |
| Required | No |
| Default | {} (generates with defaults) |
Controls generation of apiClient.ts. Set to false to skip this file entirely (useful for secondary API entries that share the first entry's client).
apiClient: {
// Output path for the generated file
outputPath: 'src/lib/apiClient.ts',
// Overrides the global cookieName for this file only
cookieName: 'accessToken',
// When true, injects x-device-id, x-device-os, x-device-browser headers
// on every request for device fingerprinting / security tracking
deviceTracking: false,
// Path to redirect to when the backend returns 401
unauthorizedRedirect: '/auth',
}
// Skip generation (secondary API entries reuse the first entry's file):
apiClient: falsefetchBackend
| | |
|---|---|
| Type | false \| object |
| Required | No |
| Default | {} (generates with defaults) |
| Applies to | nextjs and nextjs-pages |
Controls generation of fetchBackend.ts. Set to false to skip this file entirely.
fetchBackend: {
// Output path for the generated file
outputPath: 'src/lib/fetchBackend.ts',
// Overrides the global cookieName for this file only
cookieName: 'accessToken',
// Maximum time in milliseconds before a backend request times out
timeout: 15000,
}
// Skip generation:
fetchBackend: falseMultiple APIs
Pass multiple objects in the array to manage several APIs in one project. Each entry runs independently. The first entry generates the shared apiClient.ts and fetchBackend.ts; subsequent entries set those to false to reuse them.
Important: each API must have its own routesOut subdirectory. This prevents route files from different APIs overwriting each other, and also ensures the generated services call the correct URL prefix automatically:
/** @type {import('codegen-openapi').CodegenConfig[]} */
export default [
{
name: 'core',
framework: 'nextjs',
spec: 'https://api.example.com/api-json',
routesOut: 'src/app/api/core', // routes: /api/core/..., services call /api/core/...
servicesOut: 'src/services/core',
apiEnvVar: 'CORE_API_URL',
apiFallback: 'https://api.example.com',
stripPathPrefix: '/api',
cookieName: 'accessToken',
apiClient: {
outputPath: 'src/lib/apiClient.ts',
unauthorizedRedirect: '/auth',
},
fetchBackend: {
outputPath: 'src/lib/fetchBackend.ts',
timeout: 15000,
},
},
{
name: 'payments',
framework: 'nextjs',
spec: 'https://payments.example.com/api-json',
routesOut: 'src/app/api/payments', // routes: /api/payments/..., services call /api/payments/...
servicesOut: 'src/services/payments',
apiEnvVar: 'PAYMENTS_API_URL',
apiFallback: 'https://payments.example.com',
stripPathPrefix: '/api',
cookieName: 'accessToken',
apiClient: false, // reuse core's apiClient.ts
fetchBackend: false, // reuse core's fetchBackend.ts
},
];React example with different hook strategies per API:
export default [
{
name: 'main',
framework: 'react',
spec: 'https://api.example.com/api-json',
servicesOut: 'src/services',
hooksOut: 'src/hooks',
hooksMode: 'react-query', // full caching for main API
cookieName: 'accessToken',
apiClient: { outputPath: 'src/lib/apiClient.ts' },
},
{
name: 'analytics',
framework: 'react',
spec: 'https://analytics.example.com/api-json',
servicesOut: 'src/services/analytics',
hooksOut: 'src/hooks/analytics',
hooksMode: 'fetch', // lightweight for analytics
apiClient: false,
},
];What Gets Generated
Next.js App Router: Route Handlers
One route.ts file per OpenAPI path, written to routesOut. Each file acts as a typed HTTP proxy to your backend — handling path params, query strings, JSON bodies, and multipart/form-data.
src/app/api/
users/
route.ts ← GET /users, POST /users
[id]/
route.ts ← GET /users/{id}, PUT /users/{id}, DELETE /users/{id}
products/
route.ts
[id]/
route.tsDynamic route conflict resolution: When two OpenAPI paths have different parameter names at the same folder level (e.g. {id} and {userId} both under /users/), the codegen builds a path tree across all routes and normalizes them to a single canonical name — preventing the Next.js "different slug names for the same dynamic path" error.
Each handler:
- Reads
Authorizationheader from the incoming request and forwards it downstream - Parses query strings automatically
- Handles
application/jsonandmultipart/form-datarequest bodies - Returns structured
{ success: false, message }on backend errors - Catches all exceptions and returns
500with a safe error message
Next.js Pages Router: API Handlers
One .ts handler file per OpenAPI path, written to routesOut (default: pages/api). Uses the export default function handler pattern compatible with Next.js Pages Router (pre-13 or mixed projects).
pages/api/
users.ts ← GET /users, POST /users
users/
[id].ts ← GET /users/{id}, PUT /users/{id}, DELETE /users/{id}
products.ts
products/
[id].tsThe key difference from App Router: the last path segment becomes the filename (not a directory). All HTTP methods for a given path are handled inside a single switch/if on req.method.
// Auto-generated by codegen-openapi — do not edit manually
import type { NextApiRequest, NextApiResponse } from 'next';
import { fetchBackend } from '@/lib/fetchBackend';
// GET /users/{id} — Get user by ID
// PUT /users/{id} — Update user
// DELETE /users/{id} — Delete user
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const authHeader = req.headers.authorization as string | undefined;
const headers: Record<string, string> = {};
if (authHeader) {
headers['Authorization'] = authHeader;
}
const { id } = req.query as { id: string };
if (req.method === 'GET') {
const API_URL = process.env.API_URL || 'https://api.example.com';
const url = `${API_URL}/users/${id}`;
const response = await fetchBackend(url, { method: 'GET', headers });
// ... response parsing
return res.status(response.status).json(data);
}
if (req.method === 'DELETE') {
// ...
}
res.status(405).json({ success: false, message: 'Method Not Allowed' });
} catch (error) {
console.error('[handler]', error);
res.status(500).json({ success: false, message: 'Internal Server Error' });
}
}Same dynamic route conflict resolution applies — param names are normalized consistently across all paths.
Same auth/query/body handling as App Router, adapted for the req/res API.
Typed Services
One directory per OpenAPI tag, written to servicesOut. Each exports async functions bound to the generated TypeScript types.
src/services/
users/
index.ts ← getUsers(), createUser(), getUser(), updateUser(), deleteUser()
types.ts ← User, CreateUserDto, GetUsersResponse, ...
products/
index.ts
types.tsTypes are derived from your OpenAPI schemas and support:
- Primitive types:
string,number,boolean,null - Objects and nested objects
- Arrays
- Enums (as TypeScript union literals)
- Composition:
allOf,oneOf,anyOf - Nullable fields (
nullable: truein OAS 3.0,type: ['string', 'null']in OAS 3.1) $refresolution with circular reference protection
React Hooks
Only generated when framework: 'react'.
One index.ts per OpenAPI tag, written to hooksOut. The style depends on hooksMode.
src/hooks/
users/
index.ts ← useListUsers(), useGetUser(), useCreateUser(), ...
products/
index.tshooksMode: 'react-query' (default)
GET operations become useQuery hooks; mutations (POST, PUT, PATCH, DELETE) become useMutation hooks with automatic cache invalidation.
Requires @tanstack/react-query installed in your project.
// Auto-generated by codegen-openapi — do not edit manually
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import usersService from '@/services/users';
import type { ListUsersResponse, GetUserResponse, CreateUserBody, CreateUserResponse } from '@/services/users/types';
const tag = 'users';
/** List all users */
export function useListUsers() {
return useQuery<ListUsersResponse>({
queryKey: [tag],
queryFn: () => usersService.listUsers(),
});
}
/** Get user by id */
export function useGetUser(id: string) {
return useQuery<GetUserResponse>({
queryKey: [tag, id],
queryFn: () => usersService.getUser(id),
enabled: !!id,
});
}
/** Create a new user */
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation<CreateUserResponse, Error, { body: CreateUserBody }>({
mutationFn: (vars) => usersService.createUser(vars.body),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [tag] });
},
});
}Rules:
- GET →
useQuery,queryKey: [tagSlug, ...pathParams] - Mutations →
useMutation,onSuccessinvalidates the tag's queries enabled: !!paramadded automatically when path params are required
hooksMode: 'fetch'
GET operations become useState + useEffect hooks; mutations return a mutate async function. Zero extra dependencies beyond React itself.
// Auto-generated by codegen-openapi — do not edit manually
import { useState, useEffect } from 'react';
import usersService from '@/services/users';
import type { ListUsersResponse, GetUserResponse, CreateUserBody, CreateUserResponse } from '@/services/users/types';
/** List all users */
export function useListUsers() {
const [data, setData] = useState<ListUsersResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
usersService.listUsers()
.then(res => { if (!cancelled) { setData(res); setLoading(false); } })
.catch(e => { if (!cancelled) { setError(e instanceof Error ? e : new Error(String(e))); setLoading(false); } });
return () => { cancelled = true; };
}, []);
return { data, loading, error };
}
/** Get user by id */
export function useGetUser(id: string) {
const [data, setData] = useState<GetUserResponse | null>(null);
const [loading, setLoading] = useState(!!id);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
if (!id) { setLoading(false); return; }
let cancelled = false;
setLoading(true);
usersService.getUser(id)
.then(res => { if (!cancelled) { setData(res); setLoading(false); } })
.catch(e => { if (!cancelled) { setError(e instanceof Error ? e : new Error(String(e))); setLoading(false); } });
return () => { cancelled = true; };
}, [id]);
return { data, loading, error };
}
/** Create a new user */
export function useCreateUser() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const mutate = async (vars: { body: CreateUserBody }): Promise<CreateUserResponse> => {
setLoading(true);
setError(null);
try {
const result = await usersService.createUser(vars.body);
return result;
} catch (e) {
const caught = e instanceof Error ? e : new Error(String(e));
setError(caught);
throw caught;
} finally {
setLoading(false);
}
};
return { mutate, loading, error };
}Rules:
- GET →
useState+useEffect, returns{ data, loading, error } - Mutations →
mutateasync function, returns{ mutate, loading, error } - Cancellation via
cancelledflag prevents state updates on unmounted components enabled-equivalent guard: skips fetch if required path params are falsy
apiClient.ts
Generated at apiClient.outputPath (default: src/lib/apiClient.ts). Generated for both frameworks.
A pre-configured Axios instance for use in browser/client components:
- Reads the JWT from cookies on every request and attaches it as
Authorization: Bearer <token> - Intercepts
401responses and redirects tounauthorizedRedirect(default:/auth) - Optionally injects device fingerprint headers (
x-device-id,x-device-os,x-device-browser) whendeviceTracking: true
fetchBackend.ts
Generated at fetchBackend.outputPath (default: src/lib/fetchBackend.ts). Next.js (App Router and Pages Router) only.
A server-side HTTP helper for use inside route/API handlers (server-only):
- Reads the JWT from
next/headerscookies (works in Server Components and Route Handlers) - Propagates the token to outgoing backend requests as
Authorization: Bearer <token> - Configurable timeout (default: 15 seconds)
- Matches the native
fetchinterface
Contributing
Bug reports, feature requests, and pull requests are welcome.
- Issues: github.com/Last-Code-MgL/codegen-openapi/issues
- Repository: github.com/Last-Code-MgL/codegen-openapi
License
MIT
