Controllers and Routing

A controller is a class whose methods are routes. @Controller('books') is the mount path of Mounting and 404s, @Get(':id') the method and pattern, and parameter decorators pull pieces out of the request so the method never sees req: @Body(), @Param('id'), @Query(), @Headers('authorization'), @Ip(). A handler returns a value; Nest serializes it and picks the status — 200 for everything except @Post, which defaults to 201.

src/books/books.controller.ts — the routes of Section 3.13.3JavaScript
@ApiTags('Books')
@Controller({ path: 'books', version: '1' })
export class BooksController {
  constructor(private readonly books: BooksService) {}
  @Get() list(@Query() query: ListBooksDto) { return this.books.list(query); }
  @Get(':id') get(@Param('id') id: string) { return this.books.get(id); }
  @Post() @UseGuards(JwtAuthGuard, RolesGuard) @Roles('editor')
  create(@Body() dto: CreateBookDto) { return this.books.create(dto); }
  @Delete(':id') @HttpCode(204) @UseGuards(JwtAuthGuard, RolesGuard) @Roles('editor')
  remove(@Param('id') id: string) { return this.books.remove(id); }
  @Post(':id/reviews') @UseGuards(JwtAuthGuard)                 // PATCH is the same shape
  review(@Param('id') i: string, @CurrentUser() u: Caller, @Body() d: CreateReviewDto)
  { return this.books.addReview(i, u, d); } }

@HttpCode(204) is needed on the delete because remove returns undefined and Nest would otherwise answer 200 with an empty body. Nest awaits a returned promise, and an exception thrown inside becomes an HTTP response through the filter of Exception Filters — a controller holds no next(err) and no try. version: '1' plus app.enableVersioning({ type: VersioningType.URI, prefix: 'api/v' }) produces the /api/v1/... URLs of API Versioning, and at boot Nest prints the routing table it built:

Output of 106
LOG [RoutesResolver] BooksController {/books} (version: 1): +24ms
LOG [RouterExplorer] Mapped {/books, GET} ... {/books/:id, GET | PATCH | DELETE} ... route
LOG [RouterExplorer] Mapped {/books, POST} ... {/books/:id/reviews, GET | POST} route +1ms

Routes register in declaration order, so the static-before-dynamic rule of Query Strings and Match Order still applies: @Get('featured') must come before @Get(':id'). And injecting @Res() puts you back in manual mode — Nest stops sending the returned value, so forgetting res.json(...) hangs the request.