NestJS公式ドキュメント翻訳
原文
Exception filters| NestJS - A progressive Node.js web framework
例外フィルター
Nestにはアプリケーション全体で未処理のすべての例外を処理する組み込みの例外レイヤーが付属しています。例外がアプリケーションコードによって処理されない場合、このレイヤーがキャッチし、適切なユーザーフレンドリーなレスポンスを自動的に送信します。
この処理は組み込みのグローバルな例外フィルターによって実行されます。このフィルターはHttpException
型の例外(およびそのサブクラス)を処理します。例外が認識されない場合(HttpException
でもHttpException
を継承するクラスでもない場合)、組み込みの例外フィルターは次のデフォルトのJSONレスポンスを生成します。
{
"statusCode": 500,
"message": "Internal server error"
}
標準的な例外のスロー
Nestは@nestjs/common
パッケージで公開されている組み込みのHttpException
クラスを提供します。一般的なHTTP REST / GraphQL APIベースのアプリケーションでは、特定のエラー状態が発生したときに標準HTTPレスポンスオブジェクトを送信することがベストプラクティスです。たとえば、CatsController
にはfindAll()
メソッド(GET
ルートハンドラー)があります。このルートハンドラーが何らかの理由で例外をスローすると仮定しましょう。これを実証するために、次のようにハードコードしてみましょう。
@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ステータスコード -
message
:status
に基づくHTTPエラーの簡単な説明
JSONレスポンスボディのメッセージ部分のみをオーバーライドするには、response
引数に文字列を指定します。
JSONレスポンスボディ全体をオーバーライドするにはresponse
引数にオブジェクトを渡します。Nestはオブジェクトをシリアル化しJSONレスポンスボディとして返します。
2番目のコンストラクタ引数- status
-は有効なHTTPステータスコードである必要があります。ベストプラクティスは@nestjs/common
からインポートしたHttpStatus
列挙を使用することです。
レスポンスボディ全体をオーバーライドする例を次に示します。
@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は例外を認識しエラーレスポンスを自動的に処理します。そのようなカスタム例外を実装してみましょう:
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
ForbiddenException
はHttpException
を継承することで組み込みの例外ハンドラーとシームレスに連携するため、findAll()
メソッド内で使用できます。
@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
オブジェクトを使用して、送信されるレスポンスを直接制御します。
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
新しいHttpExceptionFilter
をCatsController
のcreate()
メソッドに関連付けましょう。
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
@UseFilters()
デコレータは@nestjs/common
パッケージからインポートされます。
ここでは@UseFilters()
デコレータを使用しました。@Catch()
デコレータと同様に、フィルターインスタンス、またはコンマ区切りのフィルターインスタンスのリストを使用できます。ここではHttpExceptionFilter
のインスタンスを作成しました。(インスタンスの代わりに)クラスを渡し、インスタンス化の責任をフレームワークに委ね、依存性注入を有効にすることもできます。
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
可能な場合、インスタンスの代わりにクラスを使用してフィルターを適用することをお勧めします。 Nestはモジュール全体で同じクラスのインスタンスを簡単に再利用できるため、メモリ使用量が削減されます。
上記の例では、HttpExceptionFilter
はcreate()
ルートハンドラーにのみ適用され、メソッドスコープになります。例外フィルターは、メソッドスコープ、コントローラスコープ、またはグローバルスコープのさまざまなレベルでスコープすることができます。例えば、フィルターをコントローラスコープとして設定するには次のようにします。
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
この構造はCatsController
内で定義されたすべてのルートハンドラーに対してHttpExceptionFilter
をセットアップします。
グローバルスコープのフィルターを作成するには、次の操作を行います。
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
useGlobalFilters()
メソッドはゲートウェイまたはハイブリッドアプリケーションのフィルターを設定しません。
グローバルスコープフィルターはすべてのコントローラーとすべてのルートハンドラーに対して、アプリケーション全体で使用されます。依存性注入に関して、モジュールの外部から登録されたグローバルフィルター(上記の例でのuseGlobalFilters()
)はモジュールのコンテキスト外で行われるため、依存性を注入できません。この問題を解決するには、次の構成を使用して、任意のモジュールからグローバルスコープフィルターを直接登録します。
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()
メソッドを呼び出す必要があります。
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
トークンを使用することです。