1. Request Lifecycle trong NestJS
Thứ tự xử lý một request trong NestJS:
Incoming Request
→ Middleware (global → module → route)
→ Guards (global → controller → handler)
→ Interceptors pre-handler (global → controller → handler)
→ Pipes (global → controller → handler → param)
→ Controller Handler
→ Interceptors post-handler (handler → controller → global)
→ Exception Filters (handler → controller → global)
→ Outgoing Response
2. Middleware
// common/middleware/logger.middleware.ts
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger('HTTP');
use(req: Request, res: Response, next: NextFunction) {
const { method, originalUrl, ip } = req;
const userAgent = req.get('user-agent') || '';
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const { statusCode } = res;
this.logger.log(
`${method} ${originalUrl} ${statusCode} ${duration}ms - ${ip} ${userAgent}`,
);
});
next();
}
}
// Đăng ký middleware trong AppModule
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware, CorrelationIdMiddleware)
.forRoutes('*') // Tất cả routes
.apply(AuthMiddleware)
.exclude(
{ path: 'auth/login', method: RequestMethod.POST },
{ path: 'auth/register', method: RequestMethod.POST },
)
.forRoutes({ path: '*', method: RequestMethod.ALL });
}
}
Correlation ID Middleware
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class CorrelationIdMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const correlationId = req.headers['x-correlation-id'] as string || uuidv4();
req.headers['x-correlation-id'] = correlationId;
res.setHeader('x-correlation-id', correlationId);
next();
}
}
3. Interceptors
Transform Response Interceptor
// common/interceptors/transform.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface ApiResponse<T> {
statusCode: number;
message: string;
data: T;
timestamp: string;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
const statusCode = context.switchToHttp().getResponse().statusCode;
return next.handle().pipe(
map((data) => ({
statusCode,
message: 'Success',
data,
timestamp: new Date().toISOString(),
})),
);
}
}
Performance/Timing Interceptor
@Injectable()
export class TimingInterceptor implements NestInterceptor {
private readonly logger = new Logger('Performance');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
const { method, url } = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - start;
if (duration > 1000) {
this.logger.warn(`Slow request: ${method} ${url} - ${duration}ms`);
}
}),
);
}
}
Cache Interceptor
@Injectable()
export class HttpCacheInterceptor implements NestInterceptor {
constructor(private cacheManager: Cache) {}
async intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest();
if (request.method !== 'GET') return next.handle();
const key = `cache:${request.url}`;
const cached = await this.cacheManager.get(key);
if (cached) return of(cached);
return next.handle().pipe(
tap((data) => this.cacheManager.set(key, data, 60_000)),
);
}
}
Timeout Interceptor
import { timeout, catchError } from 'rxjs/operators';
import { TimeoutError, throwError } from 'rxjs';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
constructor(private readonly timeoutMs = 5000) {}
intercept(context: ExecutionContext, next: CallHandler) {
return next.handle().pipe(
timeout(this.timeoutMs),
catchError((err) => {
if (err instanceof TimeoutError) {
throw new RequestTimeoutException('Request timed out');
}
return throwError(() => err);
}),
);
}
}
4. Đăng ký Global
// main.ts
app.useGlobalInterceptors(
new TransformInterceptor(),
new TimingInterceptor(),
);
// Hoặc qua module (hỗ trợ DI)
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{ provide: APP_INTERCEPTOR, useClass: TimingInterceptor },
],
})
export class AppModule {}
5. Tổng kết
- Middleware: Chạy đầu tiên, thích hợp cho logging, CORS, compression
- Guards: Quyết định request có được xử lý không (auth/authz)
- Interceptors: Transform response, caching, timing, error mapping
- Pipes: Validation và transformation data
- Exception Filters: Xử lý và format lỗi
Bài tiếp theo sẽ tìm hiểu WebSockets và Real-time Communication.