intermediate
NestJS controllers
Keep controllers thin by mapping transport concerns to DTOs, status codes, decorators, and service calls.
Controllers handle incoming HTTP requests and return responses. They should stay thin: map transport concerns (status codes, headers, DTOs) to service calls — no business rules in decorators.
@Controller('users')
export class UsersController {
constructor(private readonly users: UsersService) {}
@Post()
@HttpCode(201)
create(@Body() dto: CreateUserDto) {
return this.users.create(dto);
}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.users.findOne(id);
}
}
Route decorators (`@Get`, `@Post`), param decorators (`@Body`, `@Param`, `@Query`), and `@HttpCode`/`@Header` express HTTP semantics declaratively. Return values serialize to JSON unless interceptors transform them.
On interviews: justify thin controllers, when to use custom decorators, and how versioning (`@Controller({ version: '1' })`) fits API evolution.
Common pitfalls: fat controllers with DB access, ignoring HTTP status semantics, and duplicating validation that pipes should own.
The trade-off is balancing simplicity, performance, safety, and operability — name which axis you optimized and what cost you accepted.
Checklist:
- Inject services; keep controllers transport-only.
- Use DTOs and pipes at parameters.
- Set explicit status codes where defaults mislead.
- Delegate errors to filters and domain exceptions.