## The Type Gap

Server and client end up speaking different type languages.

You define a DTO on the server, write a matching type on the client, and someone renames a field. The client keeps compiling. The bug ships.

Duck Gen closes the gap.

***

## The Workflow

### Step 1: Write your server code normally

```ts
// src/auth/auth.controller.ts
@Controller('auth')
export class AuthController {
  @Post('signin')
  signin(@Body() body: SigninDto): Promise<AuthSession> {
    return this.authService.signin(body)
  }

  @Post('signup')
  signup(@Body() body: SignupDto): Promise<AuthSession> {
    return this.authService.signup(body)
  }
}
```

### Step 2: Generate types

```bash
bunx @gentleduck/gen
```

Duck Gen parses your sources with [ts-morph](https://ts-morph.com/), finds every `@Controller`, and extracts:

* HTTP method decorators (`@Get`, `@Post`, `@Put`, `@Delete`, `@Patch`)
* Route paths (controller prefix + method path)
* Parameter decorators (`@Body`, `@Query`, `@Param`, `@Headers`)
* Return types

### Step 3: Use typed routes on the client

```ts
import { createDuckQuery } from '@gentleduck/query'
import type { ApiRoutes } from './generated/api-routes'

const client = createDuckQuery<ApiRoutes>({
  baseURL: 'https://api.example.com',
})

// Fully typed — body shape, response type, route path
const session = await client.post('/api/auth/signin', {
  body: { email: 'user@example.com', password: 'secret' },
})
// session.data is AuthSession
```

***

## What Gets Generated

The output is a single `.d.ts` file with a complete route map:

```ts
// generated/api-routes.d.ts — do not edit
export interface ApiRoutes {
  '/api/auth/signin': {
    POST: { body: SigninDto; res: AuthSession }
  }
  '/api/auth/signup': {
    POST: { body: SignupDto; res: AuthSession }
  }
  '/api/users/:id': {
    GET: { params: { id: string }; res: UserProfile }
    PATCH: { params: { id: string }; body: UpdateUserDto; res: UserProfile }
  }
}
```

A pure type file — no runtime code, no bundle impact. It disappears at compile time.

***

## Configuration

```json
{
  "framework": "nestjs",
  "srcDir": "./src",
  "outputDir": "./generated",
  "apiPrefix": "/api",
  "include": ["**/*.controller.ts"],
  "exclude": ["**/*.spec.ts"]
}
```

Save this as `duck-gen.json` in your project root. Duck Gen reads it automatically.

***

## Why Not tRPC / GraphQL Codegen?

| | Duck Gen | tRPC | GraphQL Codegen |
| --- | --- | --- | --- |
| **Server framework** | NestJS (REST) | Custom router | GraphQL servers |
| **Client coupling** | None — types only | Tight coupling | Schema-first |
| **Runtime cost** | Zero (.d.ts only) | Minimal | Varies |
| **Migration effort** | Drop-in — no server changes | Full rewrite | Schema migration |
| **Existing REST APIs** | Works immediately | Not applicable | Not applicable |

Duck Gen is for teams with existing REST APIs that want type safety without a server rewrite.

Duck Gen reads your existing NestJS controllers. No new decorators, no architectural changes, no new router. The server code stays the same.

***

## Getting Started

```bash
bun add -d @gentleduck/gen    # Dev dependency — runs at build time
bun add @gentleduck/query     # Runtime — typed HTTP client
```