VeztaVezta
Guides

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

Backend (NestJS + Swagger)


OpenAPI spec (openapi.json)


Kubb (pnpm generate in vezta-fe)


lib/api/gen/
├── models/     TypeScript interfaces
├── zod/        Zod validation schemas
├── client/     Typed fetch functions
└── hooks/      TanStack Query v5 hooks


Copy to vezta-mobile/lib/api/gen/ (manual)

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:openapi

The 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 generate

This runs Kubb with the configuration in kubb.config.ts. The input source defaults to http://localhost:3001/docs-json but can be overridden with the OPENAPI_PATH environment variable.

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 Mobile

The mobile app's lib/api/gen/ directory is a direct copy of the frontend's generated code. After regenerating in vezta-fe, manually copy the output:

cp -r vezta-fe/lib/api/gen/* vezta-mobile/lib/api/gen/

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

ProblemSolution
pnpm generate fails to connectEnsure the backend is running on port 3001, or set OPENAPI_PATH to a local openapi.json file
Generated types are staleRe-export the spec (pnpm export:openapi in vezta-be) and regenerate
Missing hooks for new endpointVerify the controller has @ApiTags and @ApiOperation decorators
Mobile app types out of syncCopy vezta-fe/lib/api/gen/ to vezta-mobile/lib/api/gen/

On this page