NestJS、Prisma、PostgreSQLでREST APIを構築するシリーズの第3回チュートリアルへようこそ!このチュートリアルでは、NestJSアプリケーションでエラーハンドリングを行う方法を学びます。
目次
はじめに
このシリーズの最初の章では、新しいNestJSプロジェクトを作成し、Prisma、PostgreSQL、Swaggerと統合しました。その後、ブログアプリケーションのバックエンド用の基本的なREST APIを構築しました。第2章では、入力検証と変換を行う方法を学びました。
この章では、NestJSでエラーを処理する方法を学びます。2つの異なる戦略について説明します。
- まず、APIのコントローラー内のアプリケーションコードで直接エラーを検出し、スローする方法を学びます。
- 次に、アプリケーション全体で処理されない例外を処理するために、例外フィルターを使用する方法を学びます。
このチュートリアルでは、最初の章で構築したREST APIを使用します。このチュートリアルに従うために第2章を完了する必要はありません。
開発環境
このチュートリアルを進めるには、以下が必要です。
- ... Node.jsがインストールされていること。
- ... DockerとDocker Composeがインストールされていること。Linuxをご利用の場合、Dockerのバージョンが20.10.0以降であることを確認してください。ターミナルで
docker versionを実行してDockerのバージョンを確認できます。 - ... オプションでPrisma VS Code拡張機能がインストールされていること。Prisma VS Code拡張機能は、Prismaに優れたIntelliSenseとシンタックスハイライトを追加します。
- ... オプションでUnixシェル(LinuxやmacOSのターミナル/シェルなど)にアクセスできること(このシリーズで提供されるコマンドを実行するため)。
Unixシェルがない場合(例えば、Windowsマシンを使用している場合)でもチュートリアルを進めることはできますが、シェルコマンドはご自身のマシンに合わせて変更する必要があるかもしれません。
リポジトリをクローンする
このチュートリアルの出発点は、このシリーズのパート1の終わりです。NestJSで構築された基本的なREST APIが含まれています。
このチュートリアルの出発点は、GitHubリポジトリのend-rest-api-part-1ブランチで利用できます。始めるには、リポジトリをクローンし、end-rest-api-part-1ブランチをチェックアウトしてください。
さて、始めるために以下の手順を実行してください。
- クローンしたディレクトリに移動する
- 依存関係をインストールする
- DockerでPostgreSQLデータベースを起動する
- データベースマイグレーションを適用する
- プロジェクトを開始する
注:ステップ4では、Prisma Clientも生成され、データベースにシードデータが投入されます。
これで、APIドキュメントにhttps://:3000/api/でアクセスできるようになります。
プロジェクトの構造とファイル
クローンしたリポジトリは以下の構造を持つはずです。
このリポジトリで注目すべきファイルとディレクトリは次のとおりです。
srcディレクトリにはアプリケーションのソースコードが含まれています。3つのモジュールがあります。appモジュールはsrcディレクトリのルートにあり、アプリケーションのエントリポイントです。ウェブサーバーの起動を担当します。prismaモジュールにはPrisma Client、つまりデータベースへのインターフェースが含まれています。articlesモジュールは/articlesルートのエンドポイントと、それに付随するビジネスロジックを定義します。
prismaモジュールには以下が含まれています。schema.prismaファイルはデータベーススキーマを定義します。migrationsディレクトリにはデータベースのマイグレーション履歴が含まれています。seed.tsファイルには、開発用データベースにダミーデータをシードするためのスクリプトが含まれています。
docker-compose.ymlファイルは、PostgreSQLデータベース用のDockerイメージを定義します。.envファイルには、PostgreSQLデータベースの接続文字列が含まれています。
注:これらのコンポーネントの詳細については、このチュートリアルシリーズのパート1を参照してください。
例外を直接検出してスローする
このセクションでは、アプリケーションコードで例外を直接スローする方法を説明します。GET /articles/:idエンドポイントの問題に対処します。現在、このエンドポイントに存在しないid値を指定した場合、エラーではなくHTTP 200ステータスで何も返されません。
例えば、GET /articles/234235リクエストを試してみてください。

これを修正するには、articles.controller.tsのfindOneメソッドを変更する必要があります。記事が存在しない場合、NestJSが提供する組み込み例外であるNotFoundExceptionをスローします。
articles.controller.tsのfindOneメソッドを更新してください。
同じリクエストを再度行うと、ユーザーフレンドリーなエラーメッセージが表示されるはずです。

例外フィルターを使用して例外を処理する
専用の例外レイヤーの利点
前のセクションではエラー状態を検出し、手動で例外をスローしました。多くの場合、例外はアプリケーションコードによって自動的に生成されます。そのような場合、例外を処理し、適切なHTTPエラーをユーザーに返す必要があります。
各コントローラーで例外をケースバイケースで手動で処理することは可能ですが、多くの理由から推奨されません。
- コアアプリケーションロジックが大量のエラーハンドリングコードで cluttered(乱雑)になります。
- 多くのエンドポイントで、リソースが見つからないなどの同様のエラーに対処することになります。同じエラーハンドリングコードを多くの場所で重複させる必要があります。
- エラーハンドリングロジックが多くの場所に散らばっているため、変更が困難になります。
これらの問題を解決するために、NestJSにはアプリケーション全体で処理されない例外を処理する責任を持つ例外レイヤーがあります。NestJSでは、アプリケーション内でスローされるさまざまな種類の例外を処理する方法を定義する例外フィルターを作成できます。
NestJSグローバル例外フィルター
NestJSには、処理されないすべての例外をキャッチするグローバル例外フィルターがあります。グローバル例外フィルターを理解するために、例を見てみましょう。次のボディでPOST /articlesエンドポイントに2回リクエストを送信してください。
最初の要求は成功しますが、2回目の要求は同じtitleフィールドを持つ記事がすでに作成されているため失敗します。次のエラーが発生します。
NestJSサーバーを実行しているターミナルウィンドウを見ると、次のエラーが表示されるはずです。
ログを見ると、Prismaスキーマで@uniqueとマークされているtitleフィールドが原因で、Prisma Clientが一意制約違反エラーをスローしていることがわかります。この例外はPrismaClientKnownRequestError型であり、Prismaの名前空間レベルでエクスポートされています。
PrismaClientKnownRequestErrorはアプリケーションによって直接処理されないため、組み込みのグローバル例外フィルターによって自動的に処理されます。このフィルターはHTTP 500「Internal Server Error」レスポンスを生成します。
手動で例外フィルターを作成する
このセクションでは、先ほど見たPrismaClientKnownRequestErrorを処理するためのカスタム例外フィルターを作成します。このフィルターはPrismaClientKnownRequestError型のすべての例外をキャッチし、ユーザーに明確でわかりやすいエラーメッセージを返します。
Nest CLIを使用してフィルタークラスを生成することから始めます。
これにより、src/prisma-client-exception.filter.tsという新しいファイルが以下の内容で作成されます。
注:テスト作成用の
src/prisma-client-exception.filter.spec.tsという2つ目のファイルが作成されます。このファイルは今のところ無視して構いません。
catchメソッドが空であるため、eslintからエラーが発生します。PrismaClientExceptionFilterのcatchメソッドの実装を次のように更新してください。
ここでは以下の変更を行いました。
- このフィルターが
PrismaClientKnownRequestError型の例外をキャッチするように、@Catchデコレータに追加しました。 - 例外フィルターは、NestJSコアパッケージの
BaseExceptionFilterクラスを拡張しています。このクラスは、ユーザーに「Internal server error」レスポンスを返すcatchメソッドのデフォルト実装を提供します。これについては、NestJSのドキュメントで詳しく学ぶことができます。 - コンソールにエラーメッセージをログ出力するために、
console.errorステートメントを追加しました。これはデバッグ目的に役立ちます。
Prismaは、さまざまな種類のエラーに対してPrismaClientKnownRequestErrorをスローします。そのため、PrismaClientKnownRequestError例外からエラーコードを抽出する方法を把握する必要があります。PrismaClientKnownRequestError例外には、エラーコードを含むcodeプロパティがあります。エラーコードのリストは、Prismaエラーメッセージリファレンスで確認できます。
探しているエラーコードはP2002で、これは一意制約違反の場合に発生します。このエラーが発生した場合、HTTP 409 Conflictレスポンスをスローするようにcatchメソッドを更新します。また、ユーザーにカスタムエラーメッセージも提供します。
例外フィルターの実装をこのように更新してください。
ここでは、基盤となるフレームワークのResponseオブジェクトにアクセスし、直接レスポンスを変更しています。デフォルトでは、NestJSの内部でHTTPフレームワークとしてExpressが使用されています。P2002以外の例外コードの場合、デフォルトの「Internal server error」レスポンスを送信しています。
注:本番アプリケーションでは、エラーメッセージに機密情報がユーザーに漏洩しないように注意してください。
例外フィルターをアプリケーションに適用する
さて、PrismaClientExceptionFilterを有効にするには、特定のスコープに適用する必要があります。例外フィルターは、個別のルート(メソッドスコープ)、コントローラー全体(コントローラースコープ)、またはアプリケーション全体(グローバルスコープ)にスコープ設定できます。
main.tsファイルを更新して、例外フィルターをアプリケーション全体に適用してください。
さて、POST /articlesエンドポイントに同じリクエストを試してみてください。
今回は、よりユーザーフレンドリーなエラーメッセージが表示されます。
PrismaClientExceptionFilterはグローバルフィルターであるため、アプリケーションのすべてのルートでこの特定タイプのエラーを処理できます。
例外フィルターの実装を拡張して、他のエラーも処理することをお勧めします。たとえば、データベースにレコードが見つからない場合に発生するP2025エラーコードを処理するケースを追加できます。このエラーに対しては、ステータスコードHttpStatus.NOT_FOUNDを返す必要があります。これはPATCH /articles/:idおよびDELETE /articles/:idエンドポイントに役立ちます。
ボーナス:nestjs-prismaパッケージでPrismaの例外を処理する
これまで、NestJSアプリケーションでPrismaの例外を手動で処理するためのさまざまなテクニックを学びました。NestJSでPrismaを使用するための専用パッケージとしてnestjs-prismaというものがあり、これもPrismaの例外処理に使用できます。このパッケージは、多くのボイラープレートコードを削除するため、検討するのに優れたオプションです。
パッケージのインストールと使用方法については、nestjs-prismaのドキュメントで確認できます。このパッケージを使用する場合、個別のprismaモジュールとサービスを手動で作成する必要はありません。このパッケージが自動的に作成します。
ドキュメントの例外フィルターセクションで、このパッケージを使用してPrismaの例外を処理する方法を学ぶことができます。このチュートリアルの今後の章で、nestjs-prismaパッケージについてさらに詳しく説明します。
まとめと最終的な注意点
おめでとうございます!このチュートリアルでは、既存のNestJSアプリケーションを使用してエラーハンドリングを統合する方法を学びました。エラーを処理する2つの異なる方法、つまりアプリケーションコード内で直接処理する方法と例外フィルターを作成する方法を学びました。
この章では、Prismaのエラーを処理する方法を学びました。しかし、これらのテクニックはPrismaに限定されません。アプリケーション内のあらゆる種類のエラーを処理するために使用できます。
このチュートリアルの完成したコードは、GitHubリポジトリのend-error-handling-part-3ブランチにあります。問題に気づいた場合は、リポジトリでIssueを提起するか、PRを送信してください。また、Twitterで直接連絡することもできます。
次の投稿をお見逃しなく!
Prismaニュースレターに登録する