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.