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

レッスン 13: イ​​ンターセプター、ミドルウェア、リクエストのライフサイクル

NestJS のミドルウェア、インターセプター、リクエストのライフサイクル。ロギング、変換応答、パフォーマンス追跡、リクエスト ID。

💻 プログラミング — レッスン 13 レッスン 13: インターセプター、ミドルウェア、および リクエストのライフサイクル

NestJS: 基本から高度まで

パート 4: 高度な機能

xdev.asia

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)
  • インターセプター: 変換応答、キャッシュ、タイミング、エラー マッピング
  • パイプ: 検証および変換データ
  • 例外フィルター: エラーの処理とフォーマット

次の記事で詳しく説明します WebSocket とリアルタイム通信。