Getting Started
Wire the module into your NestJS app and generate a typed client.
Install
pnpm add -D @dudousxd/nestjs-codegen
# a validation adapter (no adapter is bundled in core) — zod shown; or -valibot / -arktype:
pnpm add -D @dudousxd/nestjs-codegen-zod
# the runtime the generated client imports its Fetcher type from:
pnpm add @dudousxd/nestjs-clientIf you want TanStack Query helpers in the generated client, install your framework adapter (you almost certainly already have it):
pnpm add @tanstack/react-query # or @tanstack/vue-query, -svelte-query, -solid-queryWire the module
This is the recommended setup. Import NestjsCodegenModule into your root module —
the codegen starts with your dev server and regenerates the client as you edit your
controllers. No config file, no extra process to run.
import { Module } from '@nestjs/common';
import { NestjsCodegenModule } from '@dudousxd/nestjs-codegen/nest';
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';
import { tanstackQuery } from '@dudousxd/nestjs-codegen-tanstack';
@Module({
imports: [
NestjsCodegenModule.forRoot({
// controllers to scan for routes + contracts
contracts: { glob: 'src/**/*.controller.ts' },
// output directory for the generated files
codegen: { outDir: 'src/generated' },
validation: zodAdapter, // zodAdapter | valibotAdapter | arktypeAdapter
extensions: [tanstackQuery()], // opt into TanStack Query handles (see below)
}),
],
})
export class AppModule {}Integrations are extensions you register in extensions: [...]. Besides
tanstackQuery() there's nestjsFilterCodegen() (typed filterQuery helpers, see
nestjs-filter) and nestjsInertiaCodegen() (Inertia router +
navigate(), see nestjs-inertia). Leave the array empty for a plain
typed-fetch client. See Extensions.
Now run your app as usual (nest start --watch) and the generated files appear in
src/generated — kept in sync while the server runs.
The watcher is a dev/CI concern, so the module skips it automatically when
NODE_ENV === 'production'. Force it with enabled: true or turn it off with
enabled: false. @nestjs/common is an optional peer dependency — your Nest app
already has it.
See Configuration for every option.
Generate in CI
In CI (before you deploy) you want a one-shot, watch-free run that fails the build if the committed client has drifted from your routes. That's the CLI:
npx nestjs-codegen codegen # one-shot
npx nestjs-codegen codegen --watch # same watcher the module runs, standaloneThe CLI reads nestjs-codegen.config.ts from your project root. To keep a single
source of truth, put your options in that file and import it into forRoot():
import { defineConfig } from '@dudousxd/nestjs-codegen';
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';
import { tanstackQuery } from '@dudousxd/nestjs-codegen-tanstack';
export default defineConfig({
contracts: { glob: 'src/**/*.controller.ts' },
codegen: { outDir: 'src/generated' },
validation: zodAdapter,
extensions: [tanstackQuery()],
});import codegenConfig from '../nestjs-codegen.config';
// ...
NestjsCodegenModule.forRoot(codegenConfig);Now the module (dev) and the CLI (CI) share one config. In CI, run the generator and fail on any diff:
npx nestjs-codegen codegen
git diff --exit-code src/generated # non-zero if the client is stalepages is Inertia-only and optional — add it only if you build a NestJS +
Inertia app (see nestjs-inertia). A plain NestJS API needs just
contracts + codegen.
Use the client
The generated api.ts exports a createApi(fetcher) factory — you create the client
once, injecting your fetcher:
import { createApi } from '../generated/api';
import { createFetcher } from '@dudousxd/nestjs-client';
export const api = createApi(createFetcher({ baseUrl: '/api' }));By default each endpoint is a plain typed fetch — call it and you get a Promise:
import { api } from '../lib/api';
const users = await api.users.list(); // typed User[]
const created = await api.users.create({ body }); // typed body + responseWant TanStack Query? Install the extension and register it — each call then returns a
handle with .queryOptions() / .mutationOptions():
pnpm add -D @dudousxd/nestjs-codegen-tanstackimport { tanstackQuery } from '@dudousxd/nestjs-codegen-tanstack';
NestjsCodegenModule.forRoot({
contracts: { glob: 'src/**/*.controller.ts' },
codegen: { outDir: 'src/generated' },
extensions: [tanstackQuery()],
});import { useQuery, useMutation } from '@tanstack/react-query';
import { api } from '../lib/api';
const users = useQuery(api.users.list().queryOptions());
const create = useMutation(api.users.create().mutationOptions());Without the extension the client is TanStack-free (plain fetch). Not using Inertia?
Just don't register the inertia extension — there's no @inertiajs/* import unless
nestjsInertiaCodegen() is in extensions: [...].