## Overview

The API routes extension scans NestJS controllers and produces a TypeScript type map.
Each key is a route path. Each value lists the HTTP method, request shape, and response
type. Clients get compile-time knowledge of every route on the server.

Duck Gen targets multiple frameworks. This page documents the **NestJS adapter**, which is
the first adapter shipped.

} title="What this means for you">
  After `duck-gen` runs, the client knows every route path, what to send, and what comes
  back. Misspelled route, wrong body shape, or missing param all fail at compile time.

## What it scans

Duck Gen looks for three things in your source files:

1. **Controller classes**: any class decorated with `@Controller()`.
2. **HTTP method decorators**: methods inside controllers decorated with `@Get`, `@Post`, etc.
3. **Parameter decorators**: `@Body`, `@Query`, `@Param`, and `@Headers` on method parameters.

```ts title="Example: what Duck Gen reads"
@Controller('users')          // controller path
export class UsersController {
  @Get(':id')                 // HTTP method + method path
  findOne(
    @Param('id') id: string,  // parameter decorator
    @Query() query: FindUserQuery,
  ): Promise<UserDto> {       // return type becomes response type
    return this.usersService.findOne(id, query)
  }
}
```

With `globalPrefix: "/api"`, this becomes a GET route at `/api/users/:id` with
`params: { id: string }`, `query: FindUserQuery`, and response type `UserDto`.

## Supported decorators

### Controller decorator

| Decorator | Description |
| --- | --- |
| `@Controller()` | No path prefix, routes start from `globalPrefix`. |
| `@Controller('users')` | Adds `/users` prefix to all routes in this controller. |
| `@Controller('admin/users')` | Nested paths work too. |

### HTTP method decorators

| Decorator | HTTP Method |
| --- | --- |
| `@Get()` | GET |
| `@Post()` | POST |
| `@Put()` | PUT |
| `@Patch()` | PATCH |
| `@Delete()` | DELETE |
| `@Options()` | OPTIONS |
| `@Head()` | HEAD |
| `@All()` | ALL |

Each decorator accepts an optional string path:

```ts
@Get()           // matches the controller path exactly
@Get('list')     // appends /list to the controller path
@Get(':id')      // appends /:id (path parameter)
@Post(':id/ban') // appends /:id/ban
```

### Parameter decorators

| Decorator | Maps to | Required? | Example type |
| --- | --- | --- | --- |
| `@Body()` | `body` | yes (POST/PUT/PATCH) | The full DTO type |
| `@Body('field')` | `body.field` | no (optional field) | `{ field?: Type }` |
| `@Query()` | `query` | no | The full query DTO |
| `@Query('page')` | `query.page` | no (optional field) | `{ page?: Type }` |
| `@Param('id')` | `params.id` | yes | `{ id: Type }` |
| `@Headers()` | `headers` | no | The full headers type |
| `@Headers('x-token')` | `headers['x-token']` | no (optional field) | `{ 'x-token'?: Type }` |

} title="Full DTO vs named property">
  `@Body()` with no argument uses the **whole parameter type** as the body. `@Body('email')`
  produces `{ email?: Type }` — an object with that one optional field. `@Query` and
  `@Headers` follow the same rule.

## Path construction rules

The final route path joins three segments:

For example:

| globalPrefix | Controller | Method | Result |
| --- | --- | --- | --- |
| `/api` | `@Controller('users')` | `@Get(':id')` | `/api/users/:id` |
| `/api` | `@Controller('admin/users')` | `@Post()` | `/api/admin/users` |
| (none) | `@Controller('auth')` | `@Post('signin')` | `/auth/signin` |
| `/api/v2` | `@Controller()` | `@Get('health')` | `/api/v2/health` |

**Rules:**

* Empty segments are removed.
* Slashes are normalized (no double slashes).
* Only **string literal** paths are supported. If a decorator uses a variable or expression,
  that route is skipped.

## Request shape rules

Each route's request type has up to four fields: `body`, `query`, `params`, and `headers`.
Fields that don't apply are set to `never` and stripped by Duck Query's `CleanupNever`
utility.

### @Body: request body

**Full body**: `@Body()` with no property key makes the parameter type the entire `body`:

```ts
@Post('signin')
signin(@Body() body: SigninDto): Promise<AuthSession> { ... }
// => body: SigninDto
```

**Named property**: `@Body('field')` creates an object with that field as optional:

```ts
@Post('update')
update(@Body('email') email: string, @Body('name') name: string): Promise<UserDto> { ... }
// => body: { email?: string } & { name?: string }
```

### @Query: query string

Works the same as `@Body`:

```ts
@Get('search')
search(@Query() query: SearchDto): Promise<SearchResult[]> { ... }
// => query: SearchDto

@Get('list')
list(@Query('page') page: number, @Query('limit') limit: number): Promise<UserDto[]> { ... }
// => query: { page?: number } & { limit?: number }
```

### @Param: path parameters

Path params are always **required** (not optional):

```ts
@Get(':id')
findOne(@Param('id') id: string): Promise<UserDto> { ... }
// => params: { id: string }

@Get(':userId/posts/:postId')
findPost(
  @Param('userId') userId: string,
  @Param('postId') postId: string,
): Promise<PostDto> { ... }
// => params: { userId: string } & { postId: string }
```

### @Headers: request headers

```ts
@Get('me')
me(@Headers('authorization') auth: string): Promise<UserDto> { ... }
// => headers: { authorization?: string }
```

### Multiple decorators of the same kind

Multiple decorators of the same kind merge into an **intersection type**:

```ts
@Post(':id/transfer')
transfer(
  @Param('id') id: string,
  @Body('amount') amount: number,
  @Body('to') to: string,
  @Query('confirm') confirm: boolean,
): Promise<TransferResult> { ... }
// => params: { id: string }
// => body: { amount?: number } & { to?: string }
// => query: { confirm?: boolean }
```

## Response types

The method's **return type** is the response type. `Promise<T>` unwraps to `T`:

```ts
@Get(':id')
findOne(@Param('id') id: string): Promise<UserDto> {
  return this.service.findOne(id)
}
// => response type: UserDto
```

} title="Always annotate return types">
  Add explicit return type annotations on controller methods. If the return type is inferred
  as `any`, Duck Gen prints a warning and converts it to `unknown` (with
  `normalizeAnyToUnknown` enabled). Explicit types produce the best output.

## Complete example

A controller with several routes and the types generated from it:

### The controller

```ts title="src/modules/users/users.controller.ts"
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common'

@Controller('users')
export class UsersController {
  constructor(private readonly service: UsersService) {}

  @Get()
  findAll(@Query() query: PaginationDto): Promise<UserDto[]> {
    return this.service.findAll(query)
  }

  @Get(':id')
  findOne(@Param('id') id: string): Promise<UserDto> {
    return this.service.findOne(id)
  }

  @Post()
  create(@Body() body: CreateUserDto): Promise<UserDto> {
    return this.service.create(body)
  }

  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() body: UpdateUserDto,
  ): Promise<UserDto> {
    return this.service.update(id, body)
  }

  @Delete(':id')
  remove(@Param('id') id: string): Promise<void> {
    return this.service.remove(id)
  }
}
```

### The generated types

With `globalPrefix: "/api"`:

```ts title="Generated: duck-gen-api-routes.d.ts (simplified)"
import type { PaginationDto } from '../../src/modules/users/users.dto'
import type { CreateUserDto } from '../../src/modules/users/users.dto'
import type { UpdateUserDto } from '../../src/modules/users/users.dto'
import type { UserDto } from '../../src/modules/users/users.dto'

export interface ApiRoutes {
  '/api/users': {
    body: never
    query: PaginationDto
    params: never
    headers: never
    res: UserDto[]
    method: 'GET'
  }
  '/api/users/:id': {
    body: never
    query: never
    params: { id: string }
    headers: never
    res: UserDto
    method: 'GET'
  } | {
    body: UpdateUserDto
    query: never
    params: { id: string }
    headers: never
    res: UserDto
    method: 'PUT'
  } | {
    body: never
    query: never
    params: { id: string }
    headers: never
    res: void
    method: 'DELETE'
  }
  '/api/users': {
    body: CreateUserDto
    query: never
    params: never
    headers: never
    res: UserDto
    method: 'POST'
  }
}
```

### Using the types on the client

```ts title="Client usage"
import type { RouteReq, RouteRes } from '@gentleduck/gen/nestjs'

// Extract types for a specific route
type CreateUserReq = RouteReq<'/api/users'>
// => { body: CreateUserDto }

type UserResponse = RouteRes<'/api/users/:id'>
// => UserDto

// Use with Duck Query for fully typed HTTP calls
import { createDuckQueryClient } from '@gentleduck/query'
import type { ApiRoutes } from '@gentleduck/gen/nestjs'

const client = createDuckQueryClient<ApiRoutes>({
  baseURL: 'http://localhost:3000',
})

// TypeScript enforces the correct request shape
const { data: users } = await client.get('/api/users', {
  query: { page: 1, limit: 10 },
})

const { data: user } = await client.get('/api/users/:id', {
  params: { id: 'u_123' },
})

const { data: created } = await client.post('/api/users', {
  body: { email: 'duck@example.com', name: 'Duck' },
})
```

## Edge cases and notes

### Dynamic paths are skipped

Duck Gen only supports **string literal** paths. This will be skipped:

```ts
const ROUTE = 'users'

@Controller(ROUTE) // skipped, not a string literal
export class UsersController { ... }
```

} title="Why string literals only?">
  Duck Gen runs at build time, not runtime. It reads the AST to find decorator arguments.
  Variables and expressions need runtime evaluation, which static analysis can't do. Keep
  decorator paths as string literals.

### Methods without HTTP decorators are ignored

Only methods with `@Get`, `@Post`, and the other HTTP decorators are scanned. Private
methods, lifecycle hooks, and helpers are skipped.

### Complex return types

Duck Gen resolves:

* Generic types like `Promise<T>`
* Union types like `UserDto | null`
* Drizzle ORM's `InferReturning` and similar utility types
* Intersection types
* Array types

The type expander walks these recursively into concrete shapes.

### Multiple controllers for the same module

All controllers in the project are scanned. An admin controller and a public controller
for the same resource produce two sets of routes:

```ts
@Controller('users')
export class UsersController { ... }

@Controller('admin/users')
export class AdminUsersController { ... }
// Both generate routes: /api/users/... and /api/admin/users/...
```

## Next steps

* [Configuration reference](/duck-gen/configuration): customize scanning and output behavior.
* [Generated types reference](/duck-gen/generated-types): learn every exported type.
* [Messages guide](/duck-gen/messages): generate typed i18n dictionaries.
* [Duck Query](/duck-query): use generated types with a type-safe HTTP client.