LoginSignup
5
2

More than 3 years have passed since last update.

Exception filters | NestJS 【翻訳】

Last updated at Posted at 2019-11-06

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Exception filters| NestJS - A progressive Node.js web framework

例外フィルター

Nestにはアプリケーション全体で未処理のすべての例外を処理する組み込みの例外レイヤーが付属しています。例外がアプリケーションコードによって処理されない場合、このレイヤーがキャッチし、適切なユーザーフレンドリーなレスポンスを自動的に送信します。

キャプチャ.PNG

この処理は組み込みのグローバルな例外フィルターによって実行されます。このフィルターはHttpException型の例外(およびそのサブクラス)を処理します。例外が認識されない場合(HttpExceptionでもHttpExceptionを継承するクラスでもない場合)、組み込みの例外フィルターは次のデフォルトのJSONレスポンスを生成します。

{
  "statusCode": 500,
  "message": "Internal server error"
}

標準的な例外のスロー

Nestは@nestjs/commonパッケージで公開されている組み込みのHttpExceptionクラスを提供します。一般的なHTTP REST / GraphQL APIベースのアプリケーションでは、特定のエラー状態が発生したときに標準HTTPレスポンスオブジェクトを送信することがベストプラクティスです。たとえば、CatsControllerにはfindAll()メソッド(GETルートハンドラー)があります。このルートハンドラーが何らかの理由で例外をスローすると仮定しましょう。これを実証するために、次のようにハードコードしてみましょう。

cats.controller.ts
@Get()
async findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

ここではHttpStatusを使用しました。これは@nestjs/commonパッケージからインポートされたヘルパー列挙です。

クライアントがこのエンドポイントを呼び出すと、レスポンスは次のようになります。

{
  "statusCode": 403,
  "message": "Forbidden"
}

HttpExceptionコンストラクタは、レスポンスを決定する2つの必須の引数を取ります。

  • response引数はJSONレスポンスボディを定義します。以下で説明するようにstringまたはobjectにすることができます。
  • status引数はHTTPステータスコードを定義します。

デフォルトではJSONレスポンスボディには2つのプロパティが含まれています。

  • statusCode:デフォルトはstatus引数で提供されるHTTPステータスコード
  • messagestatusに基づくHTTPエラーの簡単な説明

JSONレスポンスボディのメッセージ部分のみをオーバーライドするには、response引数に文字列を指定します。

JSONレスポンスボディ全体をオーバーライドするにはresponse引数にオブジェクトを渡します。Nestはオブジェクトをシリアル化しJSONレスポンスボディとして返します。

2番目のコンストラクタ引数- status -は有効なHTTPステータスコードである必要があります。ベストプラクティスは@nestjs/commonからインポートしたHttpStatus列挙を使用することです。

レスポンスボディ全体をオーバーライドする例を次に示します。

cats.controller.ts
@Get()
async findAll() {
  throw new HttpException({
    status: HttpStatus.FORBIDDEN,
    error: 'This is a custom message',
  }, 403);
}

上記を使用するとレスポンスは次のようになります。

{
  "status": 403,
  "error": "This is a custom message"
}

カスタム例外

多くの場合、独自の例外を記述する必要はなく、次のセクションで説明するように組み込みのNest HTTPエクセプションを使用できます。カスタマイズされた例外を作成する必要がある場合、独自の例外レイヤーを作成することをお勧めします。カスタム例外はHttpExceptionクラスを継承します。このアプローチにより、Nestは例外を認識しエラーレスポンスを自動的に処理します。そのようなカスタム例外を実装してみましょう:

forbidden.exception.ts
export class ForbiddenException extends HttpException {
  constructor() {
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

ForbiddenExceptionHttpExceptionを継承することで組み込みの例外ハンドラーとシームレスに連携するため、findAll()メソッド内で使用できます。

cats.controller.ts
@Get()
async findAll() {
  throw new ForbiddenException();
}

組み込みHTTPエクセプション

NestはHttpExceptionから継承する標準例外のセットを提供します。これらは@nestjs/commonパッケージから公開され、一般的なHTTP例外の多くを表します。

  • BadRequestException
  • UnauthorizedException
  • NotFoundException
  • ForbiddenException
  • NotAcceptableException
  • RequestTimeoutException
  • ConflictException
  • GoneException
  • PayloadTooLargeException
  • UnsupportedMediaTypeException
  • UnprocessableEntityException
  • InternalServerErrorException
  • NotImplementedException
  • BadGatewayException
  • ServiceUnavailableException
  • GatewayTimeoutException

例外フィルター

基本(組み込み)例外フィルターは多くのケースを自動的に処理できますが、例外レイヤーを開発者が完全に制御したい場合があります。例えば、ログを追加したり、いくつかの動的要因に基づいて異なるJSONスキーマを使用したりすることができます。例外フィルターはまさにこの目的のために設計されています。これによって、正確な制御フローとクライアントに返されるレスポンスをコントロールすることができます。

HttpExceptionクラスのインスタンスである例外をキャッチし、それらのカスタムレスポンスロジックを実装する例外フィルターを作成しましょう。これには基盤となるプラットフォームのRequestおよびResponseオブジェクトにアクセスする必要があります。Requestオブジェクトにアクセスして、元のurlを引き出してログ情報に含めてみましょう。response.json()メソッドによってResponseオブジェクトを使用して、送信されるレスポンスを直接制御します。

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,
      });
  }
}

すべての例外フィルターはExceptionFilter<T>インターフェースを実装する必要があります。これにはcatch(exception:T、host:ArgumentsHost)メソッドに指定のシグネチャを提供する必要があります。Tは例外の型を示します。

@Catch(HttpException)デコレータは必要なメタデータを例外フィルターにバインドし、この特定のフィルターがHttpException型の例外を探していることをNestに伝えます。@Catch()デコレータは単一のパラメータ、またはコンマ区切りのリストを取ることができます。これにより、複数の種類の例外のフィルターを一度に設定することができます。

Arguments host

catch()メソッドのパラメーターを見てみましょう。exceptionパラメーターは現在処理されている例外オブジェクトです。hostパラメーターはArgumentsHostオブジェクトです。ArgumentsHostは強力なユーティリティオブジェクトであり、他の章でさらに詳しく調べます※。ここでの主な目的は、(例外の発生元のコントローラー内の)元のリクエストハンドラーに渡されるRequestおよびResponseオブジェクトへの参照を提供することです。ここではArgumentsHostでいくつかのヘルパーメソッドを使用して、目的のRequestおよびResponseオブジェクトを取得しました。

host.switchToHttp()ヘルパーはHttpArgumentsHostオブジェクトを返します。HttpArgumentsHostオブジェクトには2つの便利なメソッドがあります。これらのメソッドを使用して目的のオブジェクトを抽出し、この場合はExpressの型アサーションを使用してネイティブExpress型オブジェクトを返します。

const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();

※ このレベルの抽象化をする理由は、ArgumentsHostがすべてのコンテキスト(たとえば、現在作業しているHTTPサーバコンテキストだけでなく、マイクロサービスやソケット)で機能するためです。後でArgumentsHostとそのヘルパー関数を使用して、実行コンテキストの適切な基本引数にアクセスする方法について説明します。これにより、すべてのコンテキストで動作する一般的な例外フィルターを作成できます。

Binding filters

新しいHttpExceptionFilterCatsControllercreate()メソッドに関連付けましょう。

cats.controller.ts
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

@UseFilters()デコレータは@nestjs/commonパッケージからインポートされます。

ここでは@UseFilters()デコレータを使用しました。@Catch()デコレータと同様に、フィルターインスタンス、またはコンマ区切りのフィルターインスタンスのリストを使用できます。ここではHttpExceptionFilterのインスタンスを作成しました。(インスタンスの代わりに)クラスを渡し、インスタンス化の責任をフレームワークに委ね、依存性注入を有効にすることもできます。

cats.controller.ts
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}

可能な場合、インスタンスの代わりにクラスを使用してフィルターを適用することをお勧めします。 Nestはモジュール全体で同じクラスのインスタンスを簡単に再利用できるため、メモリ使用量が削減されます。

上記の例では、HttpExceptionFiltercreate()ルートハンドラーにのみ適用され、メソッドスコープになります。例外フィルターは、メソッドスコープ、コントローラスコープ、またはグローバルスコープのさまざまなレベルでスコープすることができます。例えば、フィルターをコントローラスコープとして設定するには次のようにします。

cats.controller.ts
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

この構造はCatsController内で定義されたすべてのルートハンドラーに対してHttpExceptionFilterをセットアップします。

グローバルスコープのフィルターを作成するには、次の操作を行います。

main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

useGlobalFilters()メソッドはゲートウェイまたはハイブリッドアプリケーションのフィルターを設定しません。

グローバルスコープフィルターはすべてのコントローラーとすべてのルートハンドラーに対して、アプリケーション全体で使用されます。依存性注入に関して、モジュールの外部から登録されたグローバルフィルター(上記の例でのuseGlobalFilters())はモジュールのコンテキスト外で行われるため、依存性を注入できません。この問題を解決するには、次の構成を使用して、任意のモジュールからグローバルスコープフィルターを直接登録します。

app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

このアプローチでフィルターの依存性注入を行う場合、この方法が使われるモジュールに関係なく、フィルターは実際にはグローバルであることに注意してください。これはどこで行われるのでしょうか?フィルター(上記の例ではHttpExceptionFilter)が定義されているモジュールを選択します。また、カスタムプロバイダーの登録を処理する方法はuseClassだけではありません。詳細はこちらをご覧ください。

この方法で必要な数だけフィルターを追加できます。それぞれをプロバイダ配列に追加するだけです。

Catch everything

未処理の例外をすべて(例外の種類に関係なく)キャッチするには、@Catch()デコレータのパラメータリストを空のままにします(例:@Catch()

import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

上記の例ではフィルターはその型(クラス)に関係なく、スローされた各例外をキャッチします。

継承

通常、アプリケーション要件を満たすように完全にカスタマイズされた例外フィルターを作成するでしょう。しかし、組み込みのデフォルトのグローバル例外フィルターを単純に拡張し、特定の要因に基づいて動作をオーバーライドするというユースケースがあります。

例外処理を基本フィルターに委任するには、BaseExceptionFilterを拡張し、継承されたcatch()メソッドを呼び出す必要があります。

all-exceptions.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);
  }
}

BaseExceptionFilterを拡張したメソッドスコープおよびコントローラスコープのフィルターは、newでインスタンス化しないでください。その代わりにフレームワークがそれらを自動的にインスタンス化します。

上記の実装は一つのアプローチを示す単なるシェルです。拡張例外フィルターの実装にはビジネスロジック(さまざまな条件の処理など)が含まれます。

グローバルフィルターはベースフィルターを拡張することができます。これは2つの方法で実行できます。

一つ目の方法はカスタムグローバルフィルターをインスタンス化するときに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();

2つ目の方法は、次に示すようにAPP_FILTERトークンを使用することです。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2