1.NestJS中的請求生命週期
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. 中介軟體
// 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 });
}
}
關聯ID中介軟體
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.攔截器
轉換響應攔截器
// 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(),
})),
);
}
}
性能/定時攔截器
@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`);
}
}),
);
}
}
快取攔截器
@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)),
);
}
}
逾時攔截器
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. 註冊全球
// 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. 總結
- 中介軟體:先運行,適合日誌記錄、CORS、壓縮
- 衛兵:決定是否要處理請求(auth/authz)
- 攔截器:轉換回應、快取、計時、錯誤映射
- 管道:驗證和轉換數據
- 異常過濾器:處理和格式錯誤
下一篇文章將探討 WebSockets 和即時通信。