2022年7月19日

NestJSとPrismaでREST APIを構築する:インプットバリデーションと変換

8 min read

NestJS、Prisma、PostgreSQLを使用したREST API構築シリーズの2番目のチュートリアルへようこそ!このチュートリアルでは、APIでインプットバリデーションと変換を実行する方法を学びます。

Building a REST API with NestJS and Prisma: Input Validation & Transformation

目次

はじめに

このシリーズのパート1では、新しいNestJSプロジェクトを作成し、Prisma、PostgreSQL、Swaggerと統合しました。その後、ブログアプリケーションのバックエンド用の基本的なREST APIを構築しました。

このパートでは、API仕様に準拠するように入力を検証する方法を学びます。インプットバリデーションは、クライアントからの適切にフォーマットされたデータのみがAPIを通過するようにするために実行されます。Webアプリケーションに送信されるデータの正確性を検証することはベストプラクティスです。これは、不正な形式のデータやAPIの不正使用を防ぐのに役立ちます。

また、インプット変換を実行する方法も学びます。インプット変換は、リクエストのルートハンドラーによって処理される前に、クライアントから送信されたデータをインターセプトして変換できる技術です。これは、データを適切な型に変換したり、欠落しているフィールドにデフォルト値を適用したり、入力をサニタイズしたりするのに役立ちます。

開発環境

このチュートリアルを進めるには、以下が必要です。

  • Node.js がインストールされていること。
  • Docker または PostgreSQL がインストールされていること。
  • Prisma VSCode Extension がインストールされていること。(オプション)
  • このシリーズで提供されるコマンドを実行するために、Unixシェル(LinuxおよびmacOSのターミナル/シェルなど)にアクセスできること。(オプション)

注意:

  1. オプションのPrisma VS Code拡張機能は、Prismaに優れたIntelliSenseと構文の強調表示を追加します。

  2. Unixシェルがない場合(たとえば、Windowsマシンを使用している場合)でも、引き続き進めることができますが、シェルコマンドはマシンに合わせて変更する必要がある場合があります。

リポジトリのクローン

このチュートリアルの開始点は、このシリーズのパート1の終わりです。これには、NestJSで構築された基本的なREST APIが含まれています。このチュートリアルを開始する前に、最初のチュートリアルを完了することをお勧めします。

このチュートリアルの開始点は、begin-validationブランチのGitHubリポジトリで入手できます。開始するには、リポジトリをクローンして、begin-validationブランチをチェックアウトしてください。

開始するには、次の操作を実行してください。

  1. クローンされたディレクトリに移動します。
  1. 依存関係をインストールします。
  1. dockerでPostgreSQLデータベースを起動します。
  1. データベースのマイグレーションを適用します。
  1. プロジェクトを開始します。

注意:ステップ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を参照してください。

インプットバリデーションの実行

インプットバリデーションを実行するには、NestJS Pipesを使用します。パイプは、ルートハンドラーによって処理される引数に対して動作します。Nestはルートハンドラーの前にパイプを呼び出し、パイプはルートハンドラー宛の引数を受け取ります。パイプは、入力の検証、入力へのフィールドの追加など、さまざまなことができます。パイプはミドルウェアに似ていますが、パイプのスコープは入力引数の処理に限定されています。NestJSはすぐに使用できるパイプをいくつか提供していますが、独自のカスタムパイプを作成することもできます。

パイプには、2つの一般的なユースケースがあります。

  • バリデーション:入力データを評価し、有効な場合は変更せずにパススルーします。そうでない場合は、データが正しくない場合に例外をスローします。
  • 変換:入力データを目的の形式(例:文字列から整数)に変換します。

NestJSバリデーションパイプは、ルートに渡された引数をチェックします。引数が有効な場合、パイプは引数を変更せずにルートハンドラーに渡します。ただし、引数が指定されたバリデーションルールのいずれかに違反している場合、パイプは例外をスローします。

次の2つの図は、任意の/exampleルートのバリデーションパイプの動作を示しています。

このセクションでは、バリデーションのユースケースに焦点を当てます。

ValidationPipeをグローバルに設定

インプットバリデーションを実行するには、組み込みのNestJS ValidationPipeを使用します。ValidationPipeは、受信クライアントペイロードすべてにバリデーションルールを適用するための便利なアプローチを提供します。バリデーションルールは、class-validatorパッケージのデコレーターで宣言されます。

この機能を使用するには、プロジェクトに2つのパッケージを追加する必要があります。

class-validatorパッケージは、入力データを検証するためのデコレーターを提供し、class-transformerパッケージは、入力データを目的の形式に変換するためのデコレーターを提供します。どちらのパッケージもNestJSパイプとよく統合されています。

次に、ValidationPipemain.tsファイルにインポートし、app.useGlobalPipesメソッドを使用して、アプリケーション全体でグローバルに使用できるようにします。

CreateArticleDtoにバリデーションルールを追加

これで、class-validatorパッケージを使用して、CreateArticleDtoにバリデーションデコレーターを追加します。次のルールをCreateArticleDtoに適用します。

  1. titleは空にしたり、5文字より短くしたりすることはできません。
  2. descriptionの最大長は300文字である必要があります。
  3. bodydescriptionは空にすることはできません。
  4. titledescriptionbodystring型である必要があり、publishedboolean型である必要があります。

src/articles/dto/create-article.dto.tsファイルを開き、その内容を次のように置き換えます。

これらのルールはValidationPipeによって取得され、ルートハンドラーに自動的に適用されます。バリデーションにデコレーターを使用する利点の1つは、CreateArticleDtoPOST /articlesエンドポイントへのすべての引数の単一の真実のソースのままであることです。したがって、個別のバリデーションクラスを定義する必要はありません。

設定したバリデーションルールをテストします。POST /articlesエンドポイントを使用して、次のような非常に短いプレースホルダーtitleで記事を作成してみてください。

HTTP 400エラー応答と、応答本文に違反したバリデーションルールに関する詳細が表示されるはずです。

HTTP 400 response with descriptive error message

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

Input validation flow with ValidationPipe

クライアントリクエストから不要なプロパティを削除

CreateArticleDTOは、新しい記事を作成するためにPOST /articlesエンドポイントに送信する必要があるプロパティを定義します。UpdateArticleDTOも同様ですが、PATCH /articles/{id}エンドポイントの場合です。

現在、これらのエンドポイントの両方で、DTOで定義されていない追加のプロパティを送信できます。これにより、予期しないバグやセキュリティの問題が発生する可能性があります。たとえば、POST /articlesエンドポイントに無効なcreatedAt値とupdatedAt値を手動で渡すことができます。TypeScriptの型情報は実行時には使用できないため、アプリケーションはこれらのフィールドがDTOで使用できないことを識別できません。

例を挙げると、次のリクエストをPOST /articlesエンドポイントに送信してみてください。

このようにして、無効な値を挿入できます。ここでは、updatedAt値がcreatedAtより前の記事を作成しましたが、これは意味がありません。

これを防ぐには、クライアントリクエストから不要なフィールド/プロパティをフィルタリングする必要があります。幸い、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は、さまざまな種類の横断的関心事にデコレーターを大幅に活用することで、コードの可読性とモジュール性を向上させることを目指しています。その結果、コントローラーとサービスメソッドは、バリデーション、キャッシュ、ロギングなどの実行のためのボイラープレートコードで肥大化する必要がなくなります。

このチュートリアルの完成したコードは、end-validationブランチGitHubリポジトリにあります。問題に気付いた場合は、遠慮なくリポジトリでissueを立てるか、PRを送信してください。また、Twitterで直接私に連絡することもできます。

次回の投稿をお見逃しなく!

Prismaニュースレターにサインアップ