NestJS Controllers
A controller is the entry point for HTTP requests in a NestJS application. When a client sends a request to your server, the controller is the first part of your code that handles it. Controllers decide which method runs for which URL and HTTP method, then return a response.
The Receptionist Analogy
Think of a hotel. Guests (HTTP requests) walk in through the main entrance. The receptionist (controller) greets them, figures out what they need, and directs them to the right department — restaurant, room service, spa. The receptionist does not cook the food or clean the rooms. That work belongs to the relevant departments (services).
Controllers follow the same principle. They receive requests, extract the relevant data, call the appropriate service, and return the result. They do not contain business logic themselves.
Creating a Controller
A controller is a TypeScript class decorated with @Controller(). Generate one with the CLI:
nest g controller users
This creates users.controller.ts:
import { Controller, Get } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll() {
return 'Returns all users';
}
}
The @Controller('users') decorator sets the base path. Every route in this controller automatically starts with /users. The @Get() decorator maps the findAll() method to GET /users.
HTTP Method Decorators
NestJS provides a decorator for each HTTP method:
Decorator | HTTP Method | Example URL -------------|-------------|--------------------- @Get() | GET | GET /users @Post() | POST | POST /users @Put() | PUT | PUT /users/1 @Patch() | PATCH | PATCH /users/1 @Delete() | DELETE | DELETE /users/1
You add a path inside the decorator to extend the base path:
@Controller('users')
export class UsersController {
@Get() // GET /users
findAll() { ... }
@Get(':id') // GET /users/5
findOne() { ... }
@Post() // POST /users
create() { ... }
@Delete(':id') // DELETE /users/5
remove() { ... }
}
A Full Controller Example
Request Flow:
GET /users/42
|
v
UsersController
@Get(':id') findOne(@Param('id') id: string)
|
v
UsersService
findOne(42) → returns user object
|
v
Response: { id: 42, name: "Alice" }
Extracting Data from Requests
Controllers use parameter decorators to pull data from different parts of an HTTP request:
Decorator | What It Extracts
-------------------|-----------------------------------------
@Param('id') | URL parameter: /users/:id
@Query('search') | Query string: /users?search=alice
@Body() | Request body (JSON payload)
@Headers('auth') | A specific request header
@Req() | The full Express request object
@Res() | The full Express response object
Example using multiple decorators together:
@Post()
create(
@Body() createUserDto: CreateUserDto,
@Headers('x-request-id') requestId: string,
) {
return this.usersService.create(createUserDto);
}
Returning Responses
NestJS handles responses automatically. Whatever you return from a controller method becomes the HTTP response body:
- Return a string → sends a plain text response with status 200
- Return an object or array → serializes it to JSON automatically
- Return a Promise → NestJS waits for it to resolve, then sends the result
You do not need to call res.json() or res.send() manually in most cases. NestJS does that for you.
Setting the HTTP Status Code
By default, GET, PUT, PATCH, and DELETE return status 200. POST returns 201. You can override this with the @HttpCode() decorator:
@Delete(':id')
@HttpCode(204)
remove(@Param('id') id: string) {
return this.usersService.remove(+id);
}
Calling a Service from a Controller
The controller receives the request data and delegates work to the service via constructor injection:
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
The private readonly usersService syntax tells NestJS to inject an instance of UsersService when the controller is created. You do not instantiate the service manually. NestJS handles object creation through its dependency injection system.
Controllers Stay Thin
A well-designed controller contains minimal code. It extracts request data, passes it to the service, and returns the service's result. Any logic beyond that — database queries, data transformations, business rules — belongs in the service layer. Thin controllers are easier to test, easier to read, and easier to modify.
