1. Controller là gì?
Controllers chịu trách nhiệm xử lý incoming requests và trả về responses cho client. Routing mechanism quyết định controller nào xử lý request nào dựa trên URL path và 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. Truy cập 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 giữa NestJS Decorators và 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 (khuyến nghị)
@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. Tổng kết
- Controllers xử lý HTTP requests, được đánh dấu bằng
@Controller() - Parameter Decorators:
@Param(),@Query(),@Body(),@Headers() - Response: Return object (JSON), string (text), hoặc Promise/Observable
- DTOs: Class định nghĩa shape của data, dùng
PartialTypecho update - Versioning: URI, Header hoặc Media Type
Bài tiếp theo sẽ tìm hiểu Providers và Dependency Injection — cơ chế cốt lõi của NestJS.