Controllers
Controllers are the entry point for incoming requests in a NestJS application. Their single responsibility is routing: mapping an HTTP method and path to a handler method that produces a response. By keeping controllers focused on translating requests into calls on services, Nest enforces a clean separation between the transport layer and your business logic, which keeps each piece small and testable.
The @Controller decorator
A controller is a plain class annotated with @Controller(). The decorator accepts an optional route prefix that is prepended to every route declared inside the class. Grouping related endpoints under one prefix avoids repeating the path segment on each handler and keeps a resource’s routes together.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This returns all cats';
}
}
Here the @Controller('cats') prefix combined with @Get() registers a handler for GET /cats. Nest discovers the controller because it is listed in a module’s controllers array, instantiates it through the IoC container, and binds its routes at bootstrap.
HTTP method decorators
Each handler method is mapped to an HTTP verb with a dedicated decorator. The decorator takes an optional path argument that is appended to the controller prefix; with no argument it matches the prefix itself.
| Decorator | HTTP method | Typical use |
|---|---|---|
@Get() | GET | Read a resource or collection |
@Post() | POST | Create a new resource |
@Put() | PUT | Replace a resource |
@Patch() | PATCH | Partially update a resource |
@Delete() | DELETE | Remove a resource |
@All() | All verbs | Catch-all handler |
A controller usually exposes several of these around one resource:
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { CreateCatDto } from './dto/create-cat.dto';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll(): Cat[] {
return this.catsService.findAll();
}
@Post()
create(@Body() createCatDto: CreateCatDto): Cat {
return this.catsService.create(createCatDto);
}
}
The CatsService is injected through the constructor, so the controller delegates all real work and never owns business logic itself. The @Body() decorator extracts the parsed request body and hands it to the handler as a typed argument.
Request handling and returning values
The most idiomatic part of Nest controllers is how responses are produced. In the standard approach, you simply return a value from the handler and Nest serializes it for you. Return a string and it is sent as-is; return an object or array and Nest automatically serializes it to JSON.
@Get()
findAll(): Cat[] {
return [{ id: 1, name: 'Whiskers', age: 3 }];
}
Output:
GET /cats -> 200 OK
Content-Type: application/json
[{"id":1,"name":"Whiskers","age":3}]
Handlers may also return a Promise or an RxJS Observable; Nest resolves either before sending the response, so asynchronous data sources need no special handling at the controller layer.
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAllAsync();
}
By default a successful GET, PUT, PATCH, or DELETE returns 200, while a POST returns 201. You can override the status with @HttpCode() when the default does not fit the semantics of the endpoint.
import { Controller, Post, HttpCode } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Post()
@HttpCode(204)
create(): void {
// create the resource, return nothing
}
}
Prefer the standard return-value approach over Express-style
@Res()response objects. The moment you inject@Res(), you opt out of Nest’s serialization, interceptors, and automatic status handling for that route — use it only when you truly need direct control over the underlying response.
Scaffolding a controller
The Nest CLI generates a controller, its spec file, and wires it into the nearest module automatically, so the routing surface stays consistent across the project.
nest generate controller cats
Output:
CREATE src/cats/cats.controller.spec.ts (478 bytes)
CREATE src/cats/cats.controller.ts (97 bytes)
UPDATE src/cats/cats.module.ts (170 bytes)
Best Practices
- Keep one controller per resource and group its routes under a single
@Controller()prefix. - Treat controllers as a thin transport layer — inject services and delegate all business logic to them.
- Return plain values, Promises, or Observables and let Nest handle serialization rather than touching the raw response.
- Use the correct verb decorator for each operation and override status codes with
@HttpCode()only when needed. - Type handler return values and bodies with interfaces or DTOs so the contract is explicit and validated.
- Register every controller in exactly one module’s
controllersarray to keep ownership clear.