NestJS、Prisma、PostgreSQLでREST APIを構築するシリーズの2番目のチュートリアルへようこそ!このチュートリアルでは、APIでの入力検証と変換を実行する方法を学びます。
目次
はじめに
このシリーズの最初のパートでは、新しいNestJSプロジェクトを作成し、Prisma、PostgreSQL、Swaggerと統合しました。その後、ブログアプリケーションのバックエンドとして基本的なREST APIを構築しました。
このパートでは、API仕様に準拠するように入力データを検証する方法を学びます。入力検証は、クライアントからの適切な形式のデータのみがAPIを通過するようにするために実行されます。Webアプリケーションに送信されるデータの正確性を検証することは、ベストプラクティスです。これにより、不正な形式のデータやAPIの悪用を防ぐことができます。
また、入力変換を実行する方法も学びます。入力変換は、クライアントから送信されたデータを、そのリクエストのルートハンドラによって処理される前に傍受して変換する技術です。これは、データを適切な型に変換したり、不足しているフィールドにデフォルト値を適用したり、入力をサニタイズしたりする場合に役立ちます。
開発環境
このチュートリアルを進めるには、以下が必要です。
- Node.jsがインストールされていること。
- DockerまたはPostgreSQLがインストールされていること。
- Prisma VSCode Extensionがインストールされていること。(任意)
- このシリーズで提供されるコマンドを実行するために、Unixシェル(LinuxやmacOSのターミナル/シェルなど)にアクセスできること。(任意)
注:
オプションのPrisma VS Code拡張機能は、Prismaのための優れたIntelliSenseと構文強調表示を追加します。
Unixシェルがない場合(例えば、Windowsマシンを使用している場合)でも、チュートリアルを進めることはできますが、シェルコマンドをマシンに合わせて変更する必要があるかもしれません。
リポジトリのクローン
このチュートリアルの出発点は、このシリーズのパート1の終了時点です。NestJSで構築された基本的なREST APIが含まれています。このチュートリアルを開始する前に、最初のチュートリアルを完了することをお勧めします。
このチュートリアルの開始点は、GitHubリポジトリの`begin-validation`ブランチで利用できます。開始するには、リポジトリをクローンし、`begin-validation`ブランチをチェックアウトしてください。
さあ、始めるために以下の操作を実行してください。
- クローンしたディレクトリに移動する
- 依存関係をインストールする
- DockerでPostgreSQLデータベースを開始する
- データベースマイグレーションを適用する
- プロジェクトを開始する
注:ステップ4では、Prisma Clientの生成とデータベースのシードも行われます。
これで、https://: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を参照してください。
入力検証を実行する
入力検証を実行するには、NestJSパイプを使用します。パイプは、ルートハンドラによって処理される引数に対して動作します。Nestはルートハンドラの前にパイプを呼び出し、パイプはルートハンドラに渡される引数を受け取ります。パイプは、入力の検証、入力へのフィールドの追加など、さまざまなことができます。パイプはミドルウェアに似ていますが、パイプのスコープは入力引数の処理に限定されます。NestJSにはいくつかのパイプが標準で提供されていますが、独自のカスタムパイプを作成することもできます。
パイプには2つの典型的なユースケースがあります。
- 検証:入力データを評価し、有効な場合は変更せずに通過させます。そうでない場合は、データが正しくないときに例外をスローします。
- 変換:入力データを目的の形式に変換します(例:文字列から整数へ)。
NestJSの検証パイプは、ルートに渡された引数をチェックします。引数が有効な場合、パイプは引数を変更せずにルートハンドラに渡します。ただし、引数が指定された検証ルールに違反している場合、パイプは例外をスローします。
以下の2つの図は、任意の/exampleルートに対する検証パイプの動作を示しています。

このセクションでは、検証のユースケースに焦点を当てます。
ValidationPipeをグローバルに設定する
入力検証を実行するには、NestJSの組み込みValidationPipeを使用します。ValidationPipeは、すべての着信クライアントペイロードに対して検証ルールを適用する便利なアプローチを提供します。検証ルールはclass-validatorパッケージのデコレータで宣言されます。
この機能を使用するには、プロジェクトに2つのパッケージを追加する必要があります。
`class-validator`パッケージは入力データを検証するためのデコレータを提供し、`class-transformer`パッケージは入力データを目的の形式に変換するためのデコレータを提供します。どちらのパッケージもNestJSパイプと十分に統合されています。
次に、main.tsファイルでValidationPipeをインポートし、app.useGlobalPipesメソッドを使用してアプリケーション全体で利用可能にします。
CreateArticleDtoに検証ルールを追加する
これから、class-validatorパッケージを使用して、CreateArticleDtoに検証デコレータを追加します。CreateArticleDtoに以下のルールを適用します。
titleは空であってはならず、5文字未満であってもなりません。descriptionは最大300文字である必要があります。bodyとdescriptionは空であってはなりません。title、description、bodyはstring型であり、publishedはboolean型である必要があります。
src/articles/dto/create-article.dto.tsファイルを開き、その内容を以下に置き換えてください。
これらのルールはValidationPipeによって認識され、ルートハンドラに自動的に適用されます。検証にデコレータを使用する利点の1つは、CreateArticleDtoがPOST /articlesエンドポイントへのすべての引数にとって単一の真実の源として機能することです。したがって、別途検証クラスを定義する必要はありません。
設定した検証ルールを試してみましょう。POST /articlesエンドポイントを使用して、次のように非常に短いプレースホルダーのtitleで記事を作成してみてください。
検証ルールが破られたことの詳細がレスポンスボディに含まれたHTTP 400エラーレスポンスを受け取るはずです。

この図は、/articlesルートへの無効な入力に対してValidationPipeが内部で何をしているかを説明しています。

クライアントリクエストから不要なプロパティを削除する
CreateArticleDTOは、新しい記事を作成するためにPOST /articlesエンドポイントに送信する必要があるプロパティを定義します。UpdateArticleDTOも同様ですが、PATCH /articles/{id}エンドポイントに対してです。
現在、これらの両方のエンドポイントでは、DTOで定義されていない追加のプロパティを送信することが可能です。これは予期せぬバグやセキュリティ問題につながる可能性があります。例えば、POST /articlesエンドポイントに無効なcreatedAtとupdatedAt値を手動で渡すことができます。TypeScriptの型情報は実行時には利用できないため、アプリケーションはこれらのフィールドがDTOに存在しないことを識別できません。
例を挙げると、POST /articlesエンドポイントに以下のリクエストを送信してみてください。

このようにして、無効な値を注入することができます。ここでは、createdAtより前のupdatedAt値を持つ記事を作成しましたが、これは意味をなしません。
これを防ぐには、クライアントリクエストから不要なフィールド/プロパティをフィルタリングする必要があります。幸いなことに、NestJSはこれについても標準で機能を提供しています。必要なのは、アプリケーション内でValidationPipeを初期化する際に、whitelist: trueオプションを渡すことだけです。
このオプションをtrueに設定すると、ValidationPipeは、検証デコレータのないプロパティを意味する「非ホワイトリスト化された」すべてのプロパティを自動的に削除します。このオプションは、DTOで定義されている場合でも、検証デコレータのないすべてのプロパティをフィルタリングすることに注意することが重要です。
これで、リクエストに渡された追加のフィールド/プロパティはNestJSによって自動的に削除され、以前に示された悪用を防ぐことができます。
注:NestJSの
ValidationPipeは高度に設定可能です。利用可能なすべての設定オプションはNestJSドキュメントに記載されています。必要に応じて、アプリケーション用のカスタム検証パイプを構築することもできます。
ParseIntPipeを使用して動的URLパスを変換する
現在、API内では、GET /articles/{id}、PATCH /articles/{id}、DELETE /articles/{id}エンドポイントのidパラメータをパスの一部として受け入れています。NestJSはURLパスからidパラメータを文字列として解析します。その後、その文字列はArticlesServiceに渡される前に、アプリケーションコード内で数値にキャストされます。例えば、DELETE /articles/{id}ルートハンドラを見てください。
idが文字列型として定義されているため、Swagger APIも生成されたAPIドキュメントでこの引数を文字列としてドキュメント化しています。これは直感的でなく、誤りです。

この変換をルートハンドラ内で手動で行う代わりに、NestJSパイプを使用してidを自動的に数値に変換できます。これら3つのエンドポイントのコントローラルートハンドラに、組み込みのParseIntPipeを追加します。
ParseIntPipeは、文字列型のidパラメータをインターセプトし、適切なルートハンドラに渡す前に自動的に数値に解析します。これは、Swagger内でidパラメータを数値として正しくドキュメント化するという利点もあります。

まとめと最終コメント
おめでとうございます!このチュートリアルでは、既存のREST APIに以下の作業を行いました。
ValidationPipeを使用して検証を統合しました。- クライアントリクエストから不要なプロパティを削除しました。
ParseIntPipeを統合し、stringパス変数を解析してnumberに変換しました。
NestJSがデコレータに大きく依存していることにお気づきかもしれません。これは意図的な設計上の選択です。NestJSは、さまざまな種類の横断的な関心事に対してデコレータを最大限に活用することで、コードの可読性とモジュール性を向上させることを目指しています。その結果、コントローラやサービスメソッドは、検証、キャッシュ、ロギングなどのための定型コードで肥大化する必要がありません。
このチュートリアルの完成したコードは、GitHubリポジトリの`end-validation`ブランチにあります。問題に気づいた場合は、お気軽にリポジトリでissueを立てたり、PRを送信したりしてください。また、Twitterで直接私に連絡することもできます。
次の投稿をお見逃しなく!
Prismaニュースレターに登録する