NestJS - Exception Filters


NestJSのドキュメントを翻訳し、学習し、文章を書く.
最近のライフサイクルにおけるException Filterの範囲の良い画像です.

Exceptions Layer


Nestには、アプリケーション全体の未処理のすべての例外を処理するための例外レイヤが内蔵されています.
例外処理を単独で行わない場合は、例外レイヤに例外をキャプチャし、適切な応答を自動的に送信します.

デフォルトでは、この操作は、HttpExceptionタイプに適合するグローバルに組み込まれた例外処理フィルタによって行われます.
例外(HttpExceptionではなく、HttpExceptionから継承されたクラスでもない)が認識されない場合、exception filterはこのデフォルトの応答を発行します.
{
  "statusCode": 500,
  "message": "Internal server error"
}

Throwing standard exceptions


Nestは@nestjs/commonパッケージから公開されているHttpExceptionクラスを提供しています.
典型的なHTTP REST/GraphQL APIベースのアプリケーションでは、特定のエラーが発生した場合に、標準的なHTTP応答オブジェクトを送信することが望ましい.
たとえば、CatsControllerにはfindAll(GET)メソッドがあります.このルーティングハンドルが何らかの理由で異常を投げ出したと仮定する.次のコード
//cats.controller.ts
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
クライアントがエンドポイントを呼び出すと、次のように応答します.
{
  "statusCode": 403,
  "message": "Forbidden"
}
HttpExceptionコンストラクション関数は、2つの必須パラメータを使用して応答を決定します.
  • 応答パラメータはJSON本体であり、StringまたはObjectであってもよい.
  • statusパラメータはHTTP statusコードとして定義される.
  • デフォルトでは、JSON応答bodyには2つの属性があります.
  • statusCode:デフォルト値はステータス引数に提供されるHTTPステータスコードです.
  • メッセージ:ステータスによるHTTPエラーの簡単な説明.
  • override JSON応答ボディには、応答パラメータ(responseパラメータ)を指定するだけです.
    Nestは、シーケンス化されたオブジェクトを介してJSON応答体に戻る.
    2番目のコンストラクション関数パラメータstatusは、有効なHTTP statusコードである必要があります.
    @nestjs/commonのHttpStatus enumをインポートして使用することが望ましい.
    以下にresponse bodyを上書きする例を示します.
    // cats.controller.ts
    @Get()
    async findAll() {
      throw new HttpException({
        status: HttpStatus.FORBIDDEN,
        error: 'This is a custom message',
      }, HttpStatus.FORBIDDEN);
    }
    ということで.
    {
      "status": 403,
      "error": "This is a custom message"
    }
    このような返信を受け取ることができます.

    Custom Exception


    Nestでは、ほとんどの場合、カスタム例外を記述する必要はありません.
    Nest内蔵のHTTP Exceptionを使用すればよい.本当に必要な場合は、HttpExceptionクラスから継承された唯一のexception layerを作成することが望ましい.
    この方法を使用すると、Nestは異常を認識し、エラー応答を自動的に処理します.
    これらの実装例を見てみましょう.
    //foridden.exception.ts
    export class ForbiddenException extends HttpException {
      constructor() {
        super('Forbidden', HttpStatus.FORBIDDEN);
      }
    }
    ForbiddenExceptionはHttpExceptionを継承しているため、内蔵ExceptionハンドラはfindAll()メソッドで使用できます.以下に示します.
    // cats.controller.ts
    @Get()
    async findAll() {
      throw new ForbiddenException();
    }
    

    Built-in HTTP exceptions


    Nestは、HttpExceptionから継承される標準例外を提供します.
    @nestjs/commonパッケージに公開されており、最も一般的なHTTP異常を示しています.
  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • HttpVersionNotSupportedException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • ImATeapotException
  • MethodNotAllowedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException
  • PreconditionFailedException
  • Exception filters


    デフォルトで組み込まれているexception filterでは、多くの例外が自動的に処理されますが、exceptions layerを完全に制御したい場合があります.
    たとえば、ログを追加したり、複数の動的要因に基づいて異なるJSON schemaを使用したりすることができます.
    この目的を達成するために設計されたのがException Filterです.
    これにより、クライアントに送信される応答内容や制御の正しいプロセスを制御できます.
    //http-exception.filter.ts
    import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
    import { Request, Response } from 'express';
    
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter {
      catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
        const request = ctx.getRequest<Request>();
        const status = exception.getStatus();
    
        response
          .status(status)
          .json({
            statusCode: status,
            timestamp: new Date().toISOString(),
            path: request.url,
          });
      }
    }
    @Catch(HttpException)レコーダは、必要なメタデータをexceptionフィルタにバインドし、この特定のフィルタがNest HttpExceptionタイプにあるかどうかを確認します.
    @Catch()レコーダは、単一のパラメータまたはカンマで区切られたリストを使用できます.
    これにより、複数のタイプの例外に一度にフィルタを設定できます.

    Arguments host


    catch()メソッドのパラメータを表示します.Exceptionパラメータは、現在処理中のexceptionオブジェクトです.
    hostパラメータはArgumentsHost objectです.
    上記のコードでは、元のリクエストハンドラで通過したリクエストと応答オブジェクトの応答を取得するために使用します.ArgumentsHostは強力なユーティリティオブジェクトです.あとでexecution contextで詳しく紹介するそうです.先にスキップして

    Binding Filters


    次に、新しいHttpException FilterをCatsControllerのcreate()メソッドに接続します.
    // cats.controller.ts
    @Post()
    @UseFilters(new HttpExceptionFilter())
    async create(@Body() createCatDto: CreateCatDto) {
      throw new ForbiddenException();
    }
    
    HINT
    @UserFilters()アクセラレータは@nestjs/commonパッケージからインポートできます.
    上記のコードはnew HttpExceptionFilter()を使用してインスタンスを作成しました.
    しかし,instanceの代わりにclassを用いると,インスタンス化の責任を回避し,依存性注入を可能にすることができる.
    // cats.controller.ts
    @Post()
    @UseFilters(HttpExceptionFilter)
    async create(@Body() createCatDto: CreateCatDto) {
      throw new ForbiddenException();
    }
    HINT
    可能な場合は、インスタンスではなくクラスを使用してフィルタを適用することをお勧めします.Nestは、モジュール全体で同じクラスのインスタンスを簡単に再利用でき、メモリの使用を減らすことができます.
    // cats.controller.ts
    @UseFilters(new HttpExceptionFilter())
    export class CatsController {}
    こうやってclassを適用するのがいいそうです.これはCatsコントローラ全体に適用されます.
    グローバルスキャンフィルタを適用するには
    // main.ts
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalFilters(new HttpExceptionFilter());
      await app.listen(3000);
    }
    bootstrap();
    main.tsに適用すればよい.
    WARNING
    ユーザーグローバルフィルタ()メソッドは、ゲートウェイまたはハイブリッドアプリケーションには適用されません.
    でもmaintsのグローバル適用により依存性を注入することはできない.
    moduleのcontext内にないからです.
    この問題を解決するには、モジュールファイルに適用することをお勧めします.
    // app.module.ts
    import { Module } from '@nestjs/common';
    import { APP_FILTER } from '@nestjs/core';
    
    @Module({
      providers: [
        {
          provide: APP_FILTER,
          useClass: HttpExceptionFilter,
        },
      ],
    })
    export class AppModule {}
    これでプロバイダ配列に追加できます.

    Catch everything


    未処理のすべての例外をキャプチャする場合は、パラメータを必要とせずにCatch()Decoratorを使用します.

    Inheritance


    通常、アプリケーションのニーズを満たすために、カスタム例外フィルタを作成します.
    ただし、Nestは内蔵のexception filterを簡単に拡張し、特定の要因に基づいてoverridingを行う場合があります.
    例外処理をデフォルトのフィルタに委任するには、BaseExceptionフィルタを拡張し、継承したcatch()メソッドを呼び出すだけです.
    // all-exception.filter.ts
    import { Catch, ArgumentsHost } from '@nestjs/common';
    import { BaseExceptionFilter } from '@nestjs/core';
    
    @Catch()
    export class AllExceptionsFilter extends BaseExceptionFilter {
      catch(exception: unknown, host: ArgumentsHost) {
        super.catch(exception, host);
      }
    }
    WARNING
    BaseException Filterで拡張されたメソッドスキャンとコントローラスキャンフィルタは、新しいものにインスタンス化されません.
    フレームワークにインスタンスを自動的に生成させます.
    これに基づいて、ビジネスロジックに基づいて設定すればよい.
    グローバルフィルタはbaseフィルタに拡張できます.
    これは、カスタムグローバルフィルタをインスタンス化するときにHttpServerを挿入して参照します.
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
    
      const { httpAdapter } = app.get(HttpAdapterHost);
      app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
    
      await app.listen(3000);
    }
    bootstrap();