Aviary

Codegen

A typed-client codegen for NestJS — routes, API client, and validation schemas, generated from your controllers.

@dudousxd/nestjs-codegen reads your NestJS controllers, contracts, and DTOs and generates a fully-typed client for them: a route map, a typed API client, and client-side validation schemas. Your backend stays the single source of truth — change a controller and the client follows. It works with or without Inertia.js, and every moving part is pluggable: the validation library, the HTTP client, the serializer, and the query layer.

Coming from nestjs-inertia?

This is the same codegen, extracted into its own repo. The config file nestjs-codegen.config.ts is the successor of nestjs-inertia.config.ts (the old name is still accepted). See Getting Started.

The problem it solves

A NestJS controller already knows everything a client needs: the path, the HTTP method, the request body shape, and the response type. Yet most frontends re-state all of it by hand — hand-written fetch wrappers, duplicated route strings, a second copy of every validation rule. That copy drifts the moment a controller changes, and nothing tells you until something breaks at runtime.

nestjs-codegen closes that gap by deriving the client from the server. It discovers your routes and types once, emits typed artifacts, and keeps them in sync — in dev as you edit, and in CI as a gate that fails the build when the committed client drifts.

What you get

The codegen writes these files into your output directory:

  • routes.ts — a ROUTES map, a RouteName union, and a typed route() helper.
  • api.ts — a Tuyau-style createApi(fetcher) factory; nested by route name, fully typed.
  • forms.ts — a validation schema per validated endpoint, in your chosen lib.
  • pages.d.ts / components.json — when the Inertia integration is enabled.

Quickstart

The whole loop — install, register the module, use the client — in three steps. For the full walkthrough, including the CI flow, see Getting Started.

Install the codegen and the runtime the generated client imports its Fetcher type from:

pnpm add -D @dudousxd/nestjs-codegen @dudousxd/nestjs-codegen-zod
pnpm add @dudousxd/nestjs-client

Register NestjsCodegenModule in your root module. It starts with your dev server and regenerates the client as you edit your controllers — no config file, no extra process to run:

src/app.module.ts
import { Module } from '@nestjs/common';
import { NestjsCodegenModule } from '@dudousxd/nestjs-codegen/nest';
import { zodAdapter } from '@dudousxd/nestjs-codegen-zod';

@Module({
  imports: [
    NestjsCodegenModule.forRoot({
      contracts: { glob: 'src/**/*.controller.ts' }, // controllers to scan
      codegen: { outDir: 'src/generated' },          // where to write the client
      validation: zodAdapter,                        // zodAdapter | valibotAdapter | arktypeAdapter
    }),
  ],
})
export class AppModule {}

Run your app as usual (nest start --watch) and the generated files appear in src/generated, kept in sync while the server runs.

Create the client once, injecting your fetcher, then call your endpoints with full type safety:

src/lib/api.ts
import { createApi } from '../generated/api';
import { createFetcher } from '@dudousxd/nestjs-client';

export const api = createApi(createFetcher({ baseUrl: '/api' }));
users-page.tsx
import { api } from './lib/api';

const users = await api.users.list();              // typed User[]
const created = await api.users.create({ body });  // typed body + response

The watcher is a dev/CI concern, so the module skips it automatically when NODE_ENV === 'production'. For CI, the same generator ships as a CLI — npx nestjs-codegen codegen — to fail the build before deploy if the committed client has drifted. See CLI.

Key features

  • Pluggable validation — one neutral SchemaNode IR, rendered by zod, valibot, or arktype adapters (each a separate package). See Validation.
  • Typed API clientcreateApi(fetcher): inject your own client at runtime. See API client.
  • Bring your own fetcher — native fetch by default, or an axios instance; superjson and transformer pipelines supported. See Fetcher.
  • Extension-based integrations — register integrations in extensions: [...]; each shapes the generated api.ts. See Extensions.
    • TanStack Query (tanstackQuery()) — emits queryOptions/mutationOptions from your adapter package (react/vue/svelte/solid). See TanStack Query.
    • nestjs-filter (nestjsFilterCodegen()) — @FilterFor/@ApplyFilter → typed filterQuery() helpers. See Filters.
    • nestjs-inertia (nestjsInertiaCodegen()) — pages, shared props, and the Inertia router + navigate() helper. See Inertia.

Where to go next

On this page