Chuyển đến nội dung chính

Bài 4: Controllers và Routing trong NestJS

Hiểu Controllers, Request handling, Route parameters, Query strings, Request body, Headers. HTTP methods, status codes, redirects và sub-domain routing.

💻 Lập trình — Bài 4 Bài 4: Controllers và Routing trong NestJS

NestJS: Từ Cơ bản đến Nâng cao

Phần 1: Nền tảng NestJS

xdev.asia

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 DecoratorExpress 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 PartialType cho 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.