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.
@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:
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 +1msRoutes 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.