typed-client codegen for NestJS

Full type safety from controller to client.

Point it at your NestJS controllers and DTOs. Out come typed routes, a typed API client, and validation schemas — with pluggable validation (zod / valibot / arktype), optional TanStack Query, and a bring-your-own fetcher. Works with or without Inertia.

zod · valibot · arktype · TanStack Query · axios / superjson · works without Inertia

lib/users-client.ts✗ by hand
// hand-written client, drifts from the API
export async function getUser(id: string) {
const res = await fetch(`/users/${id}`);
return res.json() as User; // 🤞
}
// ...repeat for every endpoint, by hand
users-page.tsx✓ with codegen
import { createApi } from './generated/api';
import { createFetcher } from '@dudousxd/nestjs-client';
const api = createApi(createFetcher({ baseUrl: '/api' }));
// typed params + response, no drift
const user = await api.users.show({ params: { id } });
// opt in to TanStack Query
useQuery(api.users.show({ params: { id } }).queryOptions());

Typed wire, your stack

Everything between your NestJS controllers and your client is generated and type-checked — and every layer is yours to swap.

Pluggable validation

One neutral schema IR → zod (bundled), valibot, or arktype. Standard-Schema-shaped, so adding a lib is one render function. Your DTOs are the source of truth.

Typed API client

A Tuyau-style createApi(fetcher) factory, nested by route name. Params, body, and response inferred from your controllers — wrong calls fail at compile time.

TanStack Query, opt-in

By default each endpoint is a plain typed fetch. Turn on query and the same call exposes .queryOptions() / .mutationOptions() from your own @tanstack adapter.

Bring your own fetcher

Native fetch by default, or plug an axios instance via axiosTransport(). superjson and transformer pipelines preserve Date, Map & friends end-to-end.

nestjs-inertia integration

Optionally discover Inertia pages and shared props, augment InertiaPages, and emit a typed router navigate() — the same codegen, with Inertia switched on.

nestjs-filter integration

Discovers @FilterFor / @ApplyFilter and emits a TypedFilterQuery over your filterable fields, wired to @dudousxd/nestjs-filter-client.

Wire it in

Import the module. It runs itself.

Add NestjsCodegenModule.forRoot() to your AppModule and the codegen starts with your dev server — the typed client regenerates as you edit your controllers. No config file, no extra process.

Shipping? The same generator ships as a CLI — run nestjs-codegen codegen in CI before you deploy so the committed client can never drift from your routes.

Full setup guide
app.module.tsauto-starts in dev
import { Module } from '@nestjs/common';
import { NestjsCodegenModule } from '@dudousxd/nestjs-codegen/nest';
@Module({
imports: [
NestjsCodegenModule.forRoot({
contracts: { glob: 'src/**/*.controller.ts' },
codegen: { outDir: 'src/generated' },
}),
],
})
export class AppModule {}
terminal
# CI / pre-deploy — fail the build if the client drifts
$ npx nestjs-codegen codegen
✓ routes.ts · api.ts · forms.ts regenerated

Stop hand-writing your API client.

Generate it from the controllers you already have — typed end to end, with the validation lib, fetcher, and query layer of your choice.