Node.js、TypeScript、Nest.jsで実現する!Nest.jsにおけるInterceptor、Middleware、Filterの違い
Nest.jsにおけるInterceptor、Middleware、Filterの違い:詳細解説
Nest.jsには、アプリケーションのロジックと機能を拡張するための3つの主要なコンポーネントがあります。
- Filter:例外処理とエラーハンドリングを管理するために使用されます。予期せぬエラーが発生した場合に、適切なレスポンスを返したり、ログを記録したりするのに役立ちます。
- Middleware:リクエストを処理する前に実行されるアクションのシーケンスを定義します。ルーティング、リクエスト検証、ボディパーシングなどのタスクに適しています。
- Interceptor:リクエストとレスポンスのライフサイクル全体で横断的に処理を実行するために使用されます。認証、ロギング、キャッシュなどのタスクに最適です。
それぞれの特徴と具体的なユースケースを以下に詳しく説明します。
Interceptor
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
Middlewareは、リクエストが処理される前に実行される一連のアクションを定義します。これらのアクションは、リクエストを検査、変更、または中止するために使用できます。
- コンテンツ圧縮
レスポンスを圧縮して帯域幅を節約します。 - CORS設定
クロスオリジンリソース共有(CORS)ヘッダーを設定します。 - ボディパーシング
JSONやフォームエンコードされたデータなどのリクエストボディを解析します。 - リクエスト検証
リクエストボディ、パラメーター、ヘッダーの検証を行います。 - ルーティング
リクエストを適切なコントローラーとアクションにルーティングします。
@Middleware()
export class LoggerMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`Request received: ${req.url}`);
next();
}
}
Filter
- アラート
エラーが発生したときに管理者に通知します。 - ロギング
エラーの詳細を記録します。 - 特定のエラー処理
特定の種類のエラーを処理し、専用のエラーメッセージを返します。 - グローバルエラーハンドリング
アプリケーション全体で発生するすべてのエラーを処理します。
@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,
});
}
}
使い分けのヒント
- Filter
エラー処理とハンドリングが必要な場合に使用します。 - Middleware
リクエストを処理する前に実行する必要があるアクションを定義する場合に使用します。 - Interceptor
ロジックを横断的に適用する必要がある場合、またはリクエストとレスポンスのライフサイクル全体で処理が必要な場合に使用します。
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 {}
この例では、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 {}
Guard
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
Pipeは、リクエストデータまたはレスポンスデータをトランスフォームするために使用されます。データの検証、フォーマット、変換などのタスクに適しています。
@Injectable()
export class TrimBodyPipe implements Pipe {
transform(value: any): any {
if (typeof value === 'string') {
return value.trim();
}
return value;
}
}
Event Sourcing
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
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、カスタムデコレータなどの代替手段も検討する必要があります。
node.js typescript nestjs