API Client Generation
OpenAPI to TypeScript with Kubb code generation
Vezta uses an automated pipeline to keep the frontend and mobile API clients in sync with the backend. The backend exports an OpenAPI spec, and Kubb generates TypeScript types, Zod schemas, fetch clients, and TanStack Query hooks from it.
The Pipeline
The mobile app (when implemented) will use the same pipeline -- the generated lib/api/gen/ directory gets manually copied from vezta-fe.
Step 1: Export the OpenAPI Spec
The backend auto-exports openapi.json to its project root on every startup. You can also export it manually:
cd vezta-be && pnpm export:openapiThe spec is also available at http://localhost:3001/docs-json when the dev server is running. Swagger UI is at http://localhost:3001/docs.
Step 2: Generate the Frontend Client
With the backend running (or with a local openapi.json file):
cd vezta-fe && pnpm generateThis runs Kubb with the configuration in kubb.config.ts, reading the local openapi.json file (path via the OPENAPI_PATH env var, default ./openapi.json). pnpm generate does not fetch from the backend. To pull a fresh spec first, run pnpm generate:api, which curls ${OPENAPI_URL:-http://localhost:3001/docs-json} into openapi.json and then generates.
Kubb Configuration
The kubb.config.ts file configures five plugins in sequence:
plugins: [
// 1. Parse and validate the OpenAPI spec
pluginOas({ validate: true }),
// 2. TypeScript interfaces (enums as const, dates as string)
pluginTs({ output: { path: "models" }, enumType: "asConst", dateType: "string" }),
// 3. Zod schemas for runtime validation
pluginZod({ output: { path: "zod" }, dateType: "string", inferred: true }),
// 4. Typed fetch client functions
pluginClient({ output: { path: "client" }, client: "fetch", dataReturnType: "data" }),
// 5. TanStack Query v5 hooks (grouped by API tag, suspense enabled)
pluginReactQuery({
output: { path: "hooks" },
client: { importPath: "@kubb/plugin-client/clients/fetch" },
group: { type: "tag" },
suspense: {},
}),
]The output goes to lib/api/gen/ with clean: true, meaning the entire directory is wiped and regenerated on each run.
Fetch Alias
Both webpack and turbopack alias @kubb/plugin-client/clients/fetch to lib/api/kubb-fetch.ts in next.config.ts. This means every generated hook automatically gets:
- Bearer token injection from the in-memory access token
- Automatic 401 refresh-and-retry logic
credentials: 'include'for cookie-based auth
No hand-editing of generated code is needed.
Step 3: Sync to Docs
The docs site renders the API reference directly from public/openapi.json via fumadocs-openapi and a tag-grouped MDX generator:
# Copy the spec from the backend to the docs site
cp vezta-be/openapi.json vezta-docs/public/openapi.json
# Regenerate API reference MDX (grouped by Swagger tag)
cd vezta-docs && pnpm generate:apiThe MDX files in vezta-docs/content/api/ may be hand-edited for prose, but generated sections will be overwritten by pnpm generate:api. The interactive playground on each endpoint reads public/openapi.json directly.
Never hand-edit files in lib/api/gen/ in any subproject. The directory is wiped and regenerated with clean: true on every pnpm generate run. Any manual changes will be lost.
When to Regenerate
Run pnpm generate in vezta-fe after any backend API change:
- New endpoint added to a controller
- DTO field added, removed, or renamed
- Response shape changed
- Swagger decorators updated (
@ApiProperty,@ApiOperation,@ApiResponse) - New controller or module registered
Kubb only generates offset-based pagination hooks. Cursor-based infinite queries (used for feeds, activity logs, etc.) must be wrapped manually in feature hooks using TanStack Query's useInfiniteQuery.
Troubleshooting
| Problem | Solution |
|---|---|
pnpm generate fails to connect | Ensure the backend is running on port 3001, or set OPENAPI_PATH to a local openapi.json file |
| Generated types are stale | Re-export the spec (pnpm export:openapi in vezta-be) and regenerate |
| Missing hooks for new endpoint | Verify the controller has @ApiTags and @ApiOperation decorators |
| Docs site reference is stale | Copy vezta-be/openapi.json → vezta-docs/public/openapi.json and run pnpm generate:api |
Backend pnpm export:openapi fails with @/ alias error | Run pnpm build first, then export from dist/. The spec is also auto-exported on dev server startup. |