NestJS có hệ thống request lifecycle rõ ràng với nhiều lớp xử lý: Guard (phân quyền), Interceptor (biến đổi request/response), Pipe (validate & transform). Hiểu rõ ba khái niệm này giúp bạn xây dựng API sạch và nhất quán.
1. Request Lifecycle trong NestJS
Request
→ Middleware
→ Guards ← Có được phép không?
→ Interceptors ← Before (transform request)
→ Pipes ← Validate & transform input
→ Controller
→ Service
→ Interceptors ← After (transform response)
→ Exception Filters
Response
2. Guards — Phân quyền
Guard quyết định request có được xử lý không. Trả về true = cho qua, false = 403.
Role-based Guard
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
// Decorator để đánh dấu role cần thiết
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) return true; // Không yêu cầu role cụ thể
const { user } = context.switchToHttp().getRequest();
const hasRole = requiredRoles.some(role => user?.roles?.includes(role));
if (!hasRole) throw new ForbiddenException('Bạn không có quyền thực hiện thao tác này');
return true;
}
}
// Dùng trong Controller
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('users')
@Roles('admin')
getAllUsers() { ... }
@Delete('users/:id')
@Roles('admin', 'super-admin')
deleteUser(@Param('id') id: string) { ... }
}
Ownership Guard — Chỉ owner mới được sửa
@Injectable()
export class OwnerGuard implements CanActivate {
constructor(private postService: PostsService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const post = await this.postService.findOne(req.params.id);
if (post.userId !== req.user.id) {
throw new ForbiddenException('Bạn không phải chủ sở hữu bài viết này');
}
return true;
}
}
3. Interceptors — Biến đổi Request & Response
Interceptor dùng pattern RxJS Observable — chạy cả trước và sau controller.
Response Transform Interceptor — Chuẩn hóa format response
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, { success: boolean; data: T }> {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
);
}
}
Kết quả mọi API sẽ trả về cùng format:
{
"success": true,
"data": { ... },
"timestamp": "2025-06-01T10:00:00.000Z"
}
Logging Interceptor — Ghi log thời gian xử lý
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const { method, url } = req;
const start = Date.now();
return next.handle().pipe(
tap(() => {
const ms = Date.now() - start;
this.logger.log(`${method} ${url} — ${ms}ms`);
}),
);
}
}
Timeout Interceptor — Tự động timeout request
import { timeout, catchError } from 'rxjs/operators';
import { TimeoutError, throwError } from 'rxjs';
import { RequestTimeoutException } from '@nestjs/common';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000), // 5 giây
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException('Request timeout'));
}
return throwError(() => err);
}),
);
}
}
Cache Interceptor (tự viết)
@Injectable()
export class HttpCacheInterceptor extends CacheInterceptor {
trackBy(context: ExecutionContext): string | undefined {
const req = context.switchToHttp().getRequest();
// Không cache nếu có auth header (data user cụ thể)
if (req.headers.authorization) return undefined;
return super.trackBy(context);
}
}
4. Pipes — Validate & Transform
ParseIntPipe, ParseUUIDPipe built-in
@Get(':id')
findOne(
@Param('id', ParseUUIDPipe) id: string, // Tự validate UUID
@Query('page', new ParseIntPipe({ optional: true })) page = 1,
) { ... }
Custom Transform Pipe
@Injectable()
export class TrimStringsPipe implements PipeTransform {
transform(value: any) {
if (typeof value === 'string') return value.trim();
if (typeof value === 'object' && value !== null) {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [
k,
typeof v === 'string' ? v.trim() : v,
]),
);
}
return value;
}
}
ParseFilePipe với custom validator
import { ParseFilePipe, FileValidator } from '@nestjs/common';
export class ImageOnlyValidator extends FileValidator {
isValid(file?: Express.Multer.File): boolean {
return ['image/jpeg', 'image/png', 'image/webp'].includes(file?.mimetype ?? '');
}
buildErrorMessage(): string {
return 'Chỉ chấp nhận file ảnh (JPEG, PNG, WebP)';
}
}
// Dùng trong controller
@Post('avatar')
@UseInterceptors(FileInterceptor('file'))
uploadAvatar(
@UploadedFile(new ParseFilePipe({
validators: [new ImageOnlyValidator({})],
}))
file: Express.Multer.File,
) { ... }
5. Áp dụng Global
// main.ts
app.useGlobalGuards(new JwtAuthGuard());
app.useGlobalInterceptors(new TransformInterceptor(), new LoggingInterceptor());
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
Hoặc register qua DI (có inject dependency):
// app.module.ts
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard },
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
{ provide: APP_PIPE, useClass: ValidationPipe },
]
6. Kết luận
| Khái niệm | Vị trí | Chức năng chính |
|---|---|---|
| Guard | Trước controller | Phân quyền (có được phép không?) |
| Interceptor | Bao quanh controller | Transform request/response, logging, cache, timeout |
| Pipe | Trước controller method | Validate và transform input data |
Ba lớp này giúp tách biệt cross-cutting concerns ra khỏi business logic — controller chỉ cần lo xử lý nghiệp vụ.