Skip to main content
Search...

duck gen

Type-safe API route and message key generator for TypeScript. Scans your server code and emits .d.ts files so your client types always match your backend contracts.

What is Duck Gen?

Duck Gen is a compiler extension that reads your server source code and generates TypeScript definition files (.d.ts) describing every API route and message key it finds. Instead of writing route types by hand or hoping your client stays in sync with the server, Duck Gen automates the entire contract layer.

It is part of the @gentleduck ecosystem and is currently tested with NestJS. The architecture supports multiple frameworks. NestJS is the first adapter shipped.

The problem it solves

Without Duck Gen, keeping client types aligned with server routes looks like this:

// Server: you add a new route
@Post('signup')
signup(@Body() body: SignupDto): Promise<AuthSession> { ... }
 
// Client: you manually write the matching types... or forget to
type SignupReq = { email: string; password: string } // hope this matches SignupDto
type SignupRes = { token: string } // hope this matches AuthSession

Every time a DTO changes or a new route is added, someone has to update the client types. Duck Gen removes that step entirely.

What it generates

Duck Gen produces two categories of types:

CategoryWhat you get
API route typesA route map with typed request shapes (body, query, params, headers) and response types for every controller method.
Message registry typesStrongly-typed i18n dictionaries derived from @duckgen message tags in your code.

Both outputs are .d.ts files you import directly. No runtime cost, just types.

How it works

Loading diagram...

Here is the step-by-step flow:

  1. Load config: Duck Gen reads duck-gen.json from your project root. This tells it which framework adapter to use, where your tsconfig.json lives, and what to generate.

  2. Build the project: Using ts-morph, it creates an in-memory TypeScript project from your tsconfigPath. The include and exclude globs in your tsconfig.json determine which files get scanned.

  3. Scan source files: The NestJS adapter looks for:

    • Classes decorated with @Controller(), these become API routes.
    • Exported variables with @duckgen JSDoc tags, these become message sources.
  4. Extract type information: For each controller method, Duck Gen extracts:

    • The HTTP method (GET, POST, etc.) from decorators.
    • The full route path (globalPrefix + controller path + method path).
    • Request shape from parameter decorators (@Body, @Query, @Param, @Headers).
    • Response type from the method's return type.
  5. Emit .d.ts files: Generated type files are written to the package's generated folder and optionally to custom output directories.

Quick start

Install the package

bun add -d @gentleduck/gen

Create duck-gen.json in your project root

duck-gen.json
{
  "$schema": "node_modules/@gentleduck/gen/duck-gen.schema.json",
  "framework": "nestjs",
  "extensions": {
    "shared": {
      "includeNodeModules": false,
      "outputSource": "./generated",
      "sourceGlobs": ["src/**/*.ts", "src/**/*.tsx"],
      "tsconfigPath": "./tsconfig.json"
    },
    "apiRoutes": {
      "enabled": true,
      "globalPrefix": "/api",
      "normalizeAnyToUnknown": true,
      "outputSource": "./generated"
    },
    "messages": {
      "enabled": true,
      "outputSource": "./generated"
    }
  }
}

The $schema field gives you autocomplete and validation in your editor.

Run the generator

bunx duck-gen

You should see output like:

Config loaded
Processing done

Import and use the generated types

import type { ApiRoutes, RouteReq, RouteRes } from '@gentleduck/gen/nestjs'
 
// Now your client knows every route, request shape, and response type
type SigninRequest = RouteReq<'/api/auth/signin'>
type SigninResponse = RouteRes<'/api/auth/signin'>

Output files

Duck Gen always writes to the package's generated folder:

node_modules/@gentleduck/gen/
  generated/
    nestjs/
      duck-gen-api-routes.d.ts   # route types
      duck-gen-messages.d.ts     # message types
      index.d.ts                 # barrel export
    index.d.ts                   # top-level barrel

If you set outputSource in your config, Duck Gen also copies the generated files to those locations (e.g. ./generated in your project root).

Import paths:

// From the package entrypoint (recommended)
import type { ApiRoutes, RouteReq, RouteRes } from '@gentleduck/gen/nestjs'
 
// From a custom output directory
import type { ApiRoutes } from './generated/duck-gen-api-routes'

CLI usage

Add a script to your package.json for convenience:

package.json
{
  "scripts": {
    "generate": "duck-gen",
    "generate:watch": "duck-gen --watch"
  }
}
# Run directly
bunx duck-gen
 
# Or via script
bun run generate

CLI behavior:

  • Requires duck-gen.json in the current directory. Fails if missing.
  • Overwrites existing generated files on every run. Safe to run repeatedly.
  • Prints warnings for any return types and duplicate message const names.
  • Safe for CI/CD pipelines. Deterministic output, no side effects.
GuideWhat you will learn
ConfigurationEvery config option explained with examples.
API RoutesHow route scanning works, supported decorators, request shape rules, and examples.
MessagesHow message scanning works, tag formats, i18n type generation.
Generated TypesDeep dive into every exported type with usage examples.
Duck QueryUse generated types with a type-safe HTTP client.
TemplatesComplete NestJS + Duck Query example project.

Troubleshooting

ProblemSolution
No routes generatedMake sure decorators use string literal paths. Dynamic values are skipped.
Missing types in outputCheck that tsconfigPath points to the right file and your tsconfig.json includes the source files.
Message keys are just stringAdd as const to your message arrays or objects.
Duplicate group key warningsEach @duckgen group key must be unique across your project.
any return warningsAdd explicit return types to controller methods, or set normalizeAnyToUnknown: false.
Config file not foundRun duck-gen from the directory containing duck-gen.json.

Requirements

  • Bun 1.3.5 or newer.
  • A TypeScript project with a valid tsconfig.json.
  • NestJS (for the current tested adapter).

Contributing

Duck Gen lives in the Gentleduck monorepo. Issues, fixes, and documentation improvements are welcome at @gentleduck.