Node.js、TypeScript、Nest.jsで実現する!Nest.jsにおけるInterceptor、Middleware、Filterの違い

2024-05-21

Nest.jsにおけるInterceptor、Middleware、Filterの違い:詳細解説

Nest.jsには、アプリケーションのロジックと機能を拡張するための3つの主要なコンポーネントがあります。

  • Interceptor:リクエストとレスポンスのライフサイクル全体で横断的に処理を実行するために使用されます。認証、ロギング、キャッシュなどのタスクに最適です。
  • Middleware:リクエストを処理する前に実行されるアクションのシーケンスを定義します。ルーティング、リクエスト検証、ボディパーシングなどのタスクに適しています。
  • Filter:例外処理とエラーハンドリングを管理するために使用されます。予期せぬエラーが発生した場合に、適切なレスポンスを返したり、ログを記録したりするのに役立ちます。

それぞれの特徴と具体的なユースケースを以下に詳しく説明します。

Interceptorは、リクエストとレスポンスがアプリケーションスタックを上下する際に実行されるコードの塊です。リクエストの処理前、処理後、エラー発生時など、さまざまなタイミングで実行できます。

主なユースケース:

  • 認証と認可: ユーザーがリクエストするリソースへのアクセスを許可されているかどうかを確認します。
  • ロギング: リクエストとレスポンスに関する情報を記録します。
  • キャッシュ: 頻繁にアクセスされるデータのキャッシュを処理します。
  • トランザクション管理: データベーストランザクションを開始、コミット、ロールバックします。
  • パフォーマンスの監視: リクエストのパフォーマンスを測定し、ボトルネックを特定します。

例:

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const startTime = Date.now();

    return next.handle().finally(() => {
      const response = context.switchToHttp().getResponse();
      const elapsedTime = Date.now() - startTime;
      console.log(`Request ${request.url} took ${elapsedTime}ms`);
    });
  }
}

Middlewareは、リクエストが処理される前に実行される一連のアクションを定義します。これらのアクションは、リクエストを検査、変更、または中止するために使用できます。

  • ルーティング: リクエストを適切なコントローラーとアクションにルーティングします。
  • リクエスト検証: リクエストボディ、パラメーター、ヘッダーの検証を行います。
  • ボディパーシング: JSONやフォームエンコードされたデータなどのリクエストボディを解析します。
  • CORS設定: クロスオリジンリソース共有(CORS)ヘッダーを設定します。
  • コンテンツ圧縮: レスポンスを圧縮して帯域幅を節約します。
@Middleware()
export class LoggerMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request received: ${req.url}`);
    next();
  }
}
  • グローバルエラーハンドリング: アプリケーション全体で発生するすべてのエラーを処理します。
  • 特定のエラー処理: 特定の種類のエラーを処理し、専用のエラーメッセージを返します。
  • ロギング: エラーの詳細を記録します。
  • アラート: エラーが発生したときに管理者に通知します。
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, context: ExecutionContext) {
    const response = context.switchToHttp().getResponse();
    response.status(exception.status).json({
      message: exception.message,
    });
  }
}

使い分けのヒント

  • Interceptor: ロジックを横断的に適用する必要がある場合、またはリクエストとレスポンスのライフサイクル全体で処理が必要な場合に使用します。
  • Middleware: リクエストを処理する前に実行する必要があるアクションを定義する場合に使用します。
  • Filter: エラー処理とハンドリングが必要な場合に使用します。

これらのコンポーネントを組み合わせることで、強力で柔軟なNest.jsアプリケーションを構築




Nest.jsにおけるInterceptor、Middleware、Filterのサンプルコード

Interceptor:レスポンスログ

この例では、LoggingInterceptorを使用して、各リクエストの処理にかかった時間をログに記録します。

import { Injectable, NestInterceptor, CallHandler, ExecutionContext } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest<Request>();
    const response = context.switchToHttp().getResponse<Response>();
    const startTime = Date.now();

    return next.handle().finally(() => {
      const elapsedTime = Date.now() - startTime;
      const message = `Request ${request.method} ${request.url} took ${elapsedTime}ms`;
      console.log(message);
      response.header('X-Response-Time', elapsedTime);
    });
  }
}

このInterceptorを使用するには、app.module.tsファイルで以下のコードを追加する必要があります。

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoggingInterceptor } from './logging.interceptor';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, LoggingInterceptor],
})
export class AppModule {}

Middleware:認証

この例では、AuthMiddlewareを使用して、APIエンドポイントへのアクセスを認証します。

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: Function) {
    const authToken = req.headers['authorization'];
    if (!authToken || authToken !== 'secret123') {
      res.status(401).json({ message: 'Unauthorized' });
      return;
    }

    next();
  }
}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthMiddleware } from './auth.middleware';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
  middleware: [AuthMiddleware],
})
export class AppModule {}

Filter:グローバルエラーハンドリング

この例では、GlobalExceptionFilterを使用して、アプリケーション全体で発生するすべてのエラーを処理します。

import { Injectable, Catch, ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { HttpException } from '@nestjs/common/exceptions';
import { Request, Response } from 'express';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: any, context: ArgumentsHost) {
    const ctx = context.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const status = exception instanceof HttpException ? exception.status : 500;
    const message = exception.message || 'Unexpected error';

    response.status(status).json({
      message: message,
      stack: exception.stack,
      path: request.url,
    });
  }
}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GlobalExceptionFilter } from './global-exception.filter';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, GlobalExceptionFilter],
})
export class AppModule {}

これらの例は、Nest.jsにおけるInterceptor、Middleware、Filterの基本的な使用方法を示しています。これらの強力なツールを活用することで、アプリケーションのロジック、セキュリティ、エラー処理をより効果的に管理することができます。




Nest.jsにおけるInterceptor、Middleware、Filter以外の代替手段

Guardは、リクエストを処理する前に特定の条件を評価するために使用されます。認証、認可、ロールベースのアクセス制御などのタスクに最適です。

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const hasAuthToken = request.headers['authorization'] === 'secret123';
    if (!hasAuthToken) {
      throw new UnauthorizedException();
    }

    return true;
  }
}

Pipeは、リクエストデータまたはレスポンスデータをトランスフォームするために使用されます。データの検証、フォーマット、変換などのタスクに適しています。

@Injectable()
export class TrimBodyPipe implements Pipe {
  transform(value: any): any {
    if (typeof value === 'string') {
      return value.trim();
    }

    return value;
  }
}

Event Sourcingは、イベントストリームを使用してアプリケーションの状態を保持するアーキテクチャパターンです。状態変更をイベントとして記録し、それらを再生してアプリケーションの状態を再構築できます。

@Injectable()
export class OrderService {
  async createOrder(order: Order): Promise<void> {
    const orderCreatedEvent = new OrderCreatedEvent(order.id, order.customerId, order.items);
    await this.eventStore.publish(orderCreatedEvent);
  }
}

CQRS(Command Query Responsibility Segregation)は、読み取り操作と書き込み操作を分離するアーキテクチャパターンです。これにより、コードをよりモジュール化し、スケーラブルにすることができます。

@Injectable()
export class GetUserQueryHandler {
  handle(query: GetUserQuery): Promise<User> {
    const userId = query.userId;
    return this.userRepository.findOneById(userId);
  }
}

カスタムデコレータを使用して、独自のロジックやメタデータをクラス、プロパティ、メソッドに追加できます。

@Injectable()
export class AppController {
  @LogMethod()
  async index(): Promise<string> {
    return 'Hello, world!';
  }
}

これらの代替手段は、それぞれ異なるユースケースに適しています。具体的な状況に応じて、適切なツールを選択することが重要です。

Nest.jsは、さまざまなツールとパターンを提供することで、開発者が柔軟かつ効率的にアプリケーションを構築できるようにしています。Interceptor、Middleware、Filterは、アプリケーションのロジック、セキュリティ、エラー処理を管理するための基本的なツールですが、状況に応じてGuard、Pipe、Event Sourcing、CQRS、カスタムデコレータなどの代替手段も検討する必要があります。

これらのツールを効果的に組み合わせることで、スケーラブルで保守しやすい、かつビジネスニーズを満たすNest.jsアプリケーションを構築することができます。


node.js typescript nestjs


Node.js、Express、Pug で Web アプリケーションを作成し、JavaScript をレンダリングする

Node. js、Express、Pug を使用して、Web アプリケーションを作成する場合、インライン JavaScript をレンダリングする必要がある場合があります。これは、インタラクティブな要素や動的なコンテンツを追加するために必要です。...


【初心者でも安心】Node.jsでMongoDBモックDBを作成してユニットテストをスムーズに行う方法

Node. js で開発を行う場合、データベースとのやり取りは頻繁に行われます。しかし、本番環境のデータベースに直接アクセスしてテストを行うと、テストデータの汚染や予期せぬエラーが発生する可能性があります。そこで、モックデータベースと呼ばれるテスト専用の仮想データベースを用いることで、これらの問題を解決することができます。...


discriminated unionによるクラス型チェック

型チェックは、変数やプロパティが期待される型と一致しているかどうかを確認する処理です。TypeScript では、コンパイル時に型チェックが行われます。型チェックによって、以下の問題を検出することができます。型の間違い存在しないプロパティへのアクセス...


Node.jsでfindOne、insertOne、findOneAndUpdateを使ってMongoDBにデータを操作する

Node. jsとMongoDBを使って「FindOrCreate」操作を実現するには、いくつかの方法があります。ここでは、最も一般的な2つの方法を紹介します。方法1:findOneAndUpdateメソッドを使うこのコードは、usersコレクションにnameフィールドがJohn Doeのドキュメントが存在しない場合は挿入し、存在する場合はageとcityフィールドを更新します。...


Mongoose findOneAndUpdate で更新後のドキュメントを取得できない?原因と解決策

Node. js で MongoDB と Mongoose を使用してドキュメントを更新する場合、findOneAndUpdate メソッドは更新後のドキュメントを取得できない場合があります。これは、findOneAndUpdate メソッドがデフォルトで更新前のドキュメントを返すためです。...