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 Extension がインストールされていること。Prisma VS Code 拡張機能は、Prisma に非常に優れた IntelliSense と構文の強調表示を追加します。
- ... オプションで 、このシリーズで提供されるコマンドを実行するために、Unix シェル (Linux および macOS のターミナル/シェルなど) にアクセスできること。
Unix シェルがない場合 (たとえば、Windows マシンを使用している場合) でも、チュートリアルを進めることはできますが、シェルコマンドをマシンに合わせて変更する必要がある場合があります。
リポジトリのクローン
このチュートリアルの開始点は、このシリーズのパート 1の最後です。これには、NestJS で構築された基本的な REST API が含まれています。
このチュートリアルの開始点は、end-rest-api-part-1
ブランチの GitHub リポジトリにあります。開始するには、リポジトリをクローンして、end-rest-api-part-1
ブランチをチェックアウトします。
開始するには、次のアクションを実行します。
- クローンしたディレクトリに移動します。
- 依存関係をインストールします。
- Docker で PostgreSQL データベースを起動します。
- データベースの移行を適用します。
- プロジェクトを開始します。
注意:ステップ 4 では、Prisma Client も生成され、データベースにシードが設定されます。
これで、http://localhost:3000/api/
で API ドキュメントにアクセスできるようになります。
プロジェクトの構造とファイル
クローンしたリポジトリの構造は次のようになります。
このリポジトリの注目すべきファイルとディレクトリは次のとおりです。
src
ディレクトリには、アプリケーションのソースコードが含まれています。3 つのモジュールがあります。app
モジュールは、src
ディレクトリのルートにあり、アプリケーションのエントリポイントです。これは、Web サーバーを起動する役割を担います。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 エラーをユーザーに返す必要があります。
各コントローラーで例外をケースバイケースで手動で処理することも可能ですが、多くの理由から良い考えではありません。
- コアアプリケーションロジックが、多くのエラー処理コードで煩雑になります。
- エンドポイントの多くは、リソースが見つからないなど、同様のエラーを処理します。同じエラー処理コードを多くの場所で複製する必要があります。
- エラー処理ロジックが多くの場所に分散しているため、変更が困難になります。
これらの問題を解決するために、NestJS には、アプリケーション全体で処理されない例外を処理する責任を負う例外レイヤーがあります。NestJS では、アプリケーション内でスローされるさまざまな種類の例外を処理する方法を定義する例外フィルタを作成できます。
NestJS グローバル例外フィルタ
NestJS には、処理されないすべての例外をキャッチするグローバル例外フィルタがあります。グローバル例外フィルタを理解するために、例を見てみましょう。次の本文を使用して、POST /articles
エンドポイントに2 つのリクエストを送信します。
最初の要求は成功しますが、2 番目の要求は、同じ title
フィールドを持つ記事を既に作成しているため失敗します。次のエラーが表示されます。
NestJS サーバーを実行しているターミナルウィンドウを見ると、次のエラーが表示されるはずです。
ログから、Prisma スキーマで @unique
としてマークされている title
フィールドが原因で、Prisma Client が一意制約検証エラーをスローしていることがわかります。例外の型は PrismaClientKnownRequestError
であり、Prisma 名前空間レベルでエクスポートされます。
PrismaClientKnownRequestError
はアプリケーションによって直接処理されていないため、組み込みのグローバル例外フィルタによって自動的に処理されます。このフィルタは、HTTP 500
「内部サーバーエラー」応答を生成します。
手動例外フィルタを作成する
このセクションでは、表示された 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
クラスを拡張します。このクラスは、ユーザーに「内部サーバーエラー」応答を返すcatch
メソッドのデフォルト実装を提供します。詳細については、NestJS ドキュメントを参照してください。 - エラーメッセージをコンソールに記録するために、
console.error
ステートメントを追加しました。これは、デバッグの目的で役立ちます。
Prisma は、さまざまな種類のエラーに対して PrismaClientKnownRequestError
をスローします。したがって、PrismaClientKnownRequestError
例外からエラーコードを抽出する方法を理解する必要があります。PrismaClientKnownRequestError
例外には、エラーコードを含む code
プロパティがあります。Prisma エラーメッセージリファレンスでエラーコードのリストを見つけることができます。
探しているエラーコードは P2002
で、一意制約違反の場合に発生します。エラーが発生した場合に HTTP 409 Conflict
応答をスローするように catch
メソッドを更新します。また、カスタムエラーメッセージをユーザーに提供します。
次のように例外フィルタの実装を更新します。
ここでは、基盤となるフレームワーク Response
オブジェクトにアクセスし、応答を直接変更しています。デフォルトでは、express は NestJS の下で使用される HTTP フレームワークです。P2002
以外の例外コードについては、デフォルトの「内部サーバーエラー」応答を送信しています。
注意:本番アプリケーションでは、エラーメッセージでユーザーに機密情報を漏洩しないように注意してください。
例外フィルタをアプリケーションに適用する
次に、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 に限定されません。これらを使用して、アプリケーション内の任意のエラータイプを処理できます。
このチュートリアルの完成したコードは、end-error-handling-part-3
ブランチの GitHub リポジトリにあります。問題に気付いた場合は、リポジトリに issue を作成するか、PR を送信してください。また、Twitter で直接私に連絡することもできます。
次回の投稿をお見逃しなく!
Prisma ニュースレターにサインアップ