1. What is a Controller?
Controllers are responsible for processing incoming requests and return responses. responses for clients. Routing mechanism decides which controller handles which request based on URL path and HTTP method.
import { Controller, Get, Post, Put, Patch, Delete } from '@nestjs/common';
@Controller('products') // Base route: /products
export class ProductsController {
@Get() // GET /products
findAll() { return 'Tất cả sản phẩm'; }
@Get(':id') // GET /products/123
findOne() { return 'Một sản phẩm'; }
@Post() // POST /products
create() { return 'Tạo sản phẩm'; }
@Put(':id') // PUT /products/123
replace() { return 'Thay thế sản phẩm'; }
@Patch(':id') // PATCH /products/123
update() { return 'Cập nhật sản phẩm'; }
@Delete(':id') // DELETE /products/123
remove() { return 'Xóa sản phẩm'; }
}
2. Access Request Object
Parameter Decorators
import {
Controller, Get, Post, Param, Query, Body,
Headers, Ip, Req, Res, HttpCode, Header,
} from '@nestjs/common';
import { Request, Response } from 'express';
@Controller('users')
export class UsersController {
// Route Parameters
@Get(':id')
findOne(@Param('id') id: string) { // /users/42 → id = '42'
return `User #${id}`;
}
// Multiple Route Params
@Get(':userId/posts/:postId')
findUserPost(
@Param('userId') userId: string,
@Param('postId') postId: string,
) {
return `User ${userId}, Post ${postId}`;
}
// Query Parameters
@Get()
findAll(
@Query('page') page: number = 1, // /users?page=2
@Query('limit') limit: number = 10, // /users?page=2&limit=20
@Query('search') search?: string, // /users?search=john
@Query() allQuery: Record<string, any>, // Tất cả query params
) {
return { page, limit, search };
}
// Request Body
@Post()
create(@Body() body: CreateUserDto) { // Full body
return body;
}
@Post('partial')
createPartial(@Body('name') name: string) { // Chỉ lấy field 'name'
return { name };
}
// Headers
@Get('info')
getInfo(
@Headers('authorization') auth: string,
@Headers('user-agent') userAgent: string,
@Ip() ip: string,
) {
return { auth, userAgent, ip };
}
// Full Request/Response objects (ít dùng)
@Get('raw')
getRaw(@Req() req: Request, @Res() res: Response) {
res.status(200).json({ url: req.url, method: req.method });
}
}
Mapping between NestJS Decorators and Express
| NestJS Decorator | Express equivalent |
|---|---|
@Req() | req |
@Res() | res |
@Param(key) | req.params[key] |
@Query(key) | req.query[key] |
@Body(key) | req.body[key] |
@Headers(key) | req.headers[key] |
@Ip() | req.ip |
@Session() | req.session |
3. Response Handling
Standard Approach (recommended)
@Controller('products')
export class ProductsController {
// Return object → tự động serialize thành JSON
@Get()
findAll(): Product[] {
return [{ id: 1, name: 'Laptop' }];
// Response: 200 OK, Content-Type: application/json
}
// Return string → plain text
@Get('health')
health(): string {
return 'OK';
// Response: 200 OK, Content-Type: text/html
}
// Return Promise → NestJS tự await
@Get('async')
async findAsync(): Promise<Product[]> {
return await this.productService.findAll();
}
// Return Observable → NestJS tự subscribe
@Get('stream')
findStream(): Observable<Product[]> {
return this.productService.findAllStream();
}
}
Custom Status Codes
import { HttpCode, HttpStatus } from '@nestjs/common';
@Post()
@HttpCode(HttpStatus.CREATED) // 201
create(@Body() dto: CreateProductDto) {
return this.productService.create(dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT) // 204
remove(@Param('id') id: string) {
this.productService.remove(id);
}
Custom Response Headers
@Get()
@Header('Cache-Control', 'max-age=3600')
@Header('X-Custom-Header', 'NestJS')
findAll() {
return this.productService.findAll();
}
Redirects
import { Redirect } from '@nestjs/common';
@Get('old-page')
@Redirect('/new-page', 301) // Permanent redirect
oldPage() {}
// Dynamic redirect
@Get('docs')
@Redirect('https://docs.nestjs.com', 302)
getDocs(@Query('version') version: string) {
if (version === 'v11') {
return { url: 'https://docs.nestjs.com/v11' };
}
// Nếu không return gì, dùng URL mặc định
}
4. Route Wildcards & Pattern Matching
// Wildcard routes
@Get('ab*cd') // Matches: abcd, ab_cd, abecd, ab123cd,...
findWild() {
return 'This route uses a wildcard';
}
// Regex-based param (NestJS dùng path-to-regexp)
@Get(':id(\\d+)') // Chỉ match số: /products/123 ✓, /products/abc ✗
findOne(@Param('id') id: string) {
return `Product #${id}`;
}
5. DTOs — Data Transfer Objects
// src/products/dto/create-product.dto.ts
export class CreateProductDto {
name: string;
description: string;
price: number;
category: string;
inStock: boolean;
}
// src/products/dto/update-product.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';
// PartialType tạo class mới với tất cả fields optional
export class UpdateProductDto extends PartialType(CreateProductDto) {}
// Sử dụng
@Post()
create(@Body() dto: CreateProductDto) {
// TypeScript đảm bảo dto có đúng shape
return this.service.create(dto);
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateProductDto) {
return this.service.update(+id, dto);
}
6. API Versioning
// main.ts — Bật versioning
import { VersioningType } from '@nestjs/common';
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI, // /v1/users, /v2/users
// type: VersioningType.HEADER, // Header: X-API-Version: 1
// type: VersioningType.MEDIA_TYPE, // Accept: application/json;v=1
});
// Controller
@Controller({ path: 'users', version: '1' })
export class UsersV1Controller {
@Get()
findAll() { return 'V1 users'; }
}
@Controller({ path: 'users', version: '2' })
export class UsersV2Controller {
@Get()
findAll() { return 'V2 users with pagination'; }
}
// Per-route versioning
@Controller('users')
export class UsersController {
@Version('1')
@Get()
findAllV1() { return 'V1'; }
@Version('2')
@Get()
findAllV2() { return 'V2'; }
}
7. Summary
- Controllers handle HTTP requests, marked with
@Controller() - Parameter Decorators:
@Param(),@Query(),@Body(),@Headers() - Response: Return object (JSON), string (text), or Promise/Observable
- DTOs: Class defines the shape of data, used
PartialTypefor updates - Versioning: URI, Header or Media Type
The next article will explore Providers and Dependency Injection — the core mechanism of NestJS.