NestJS、Prisma、PostgreSQLを使用したREST API構築シリーズの2番目のチュートリアルへようこそ!このチュートリアルでは、APIでインプットバリデーションと変換を実行する方法を学びます。
目次
はじめに
このシリーズのパート1では、新しい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が含まれています。このチュートリアルを開始する前に、最初のチュートリアルを完了することをお勧めします。
このチュートリアルの開始点は、begin-validationブランチのGitHubリポジトリで入手できます。開始するには、リポジトリをクローンして、begin-validation
ブランチをチェックアウトしてください。
開始するには、次の操作を実行してください。
- クローンされたディレクトリに移動します。
- 依存関係をインストールします。
- 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を参照してください。
インプットバリデーションの実行
インプットバリデーションを実行するには、NestJS Pipesを使用します。パイプは、ルートハンドラーによって処理される引数に対して動作します。Nestはルートハンドラーの前にパイプを呼び出し、パイプはルートハンドラー宛の引数を受け取ります。パイプは、入力の検証、入力へのフィールドの追加など、さまざまなことができます。パイプはミドルウェアに似ていますが、パイプのスコープは入力引数の処理に限定されています。NestJSはすぐに使用できるパイプをいくつか提供していますが、独自のカスタムパイプを作成することもできます。
パイプには、2つの一般的なユースケースがあります。
- バリデーション:入力データを評価し、有効な場合は変更せずにパススルーします。そうでない場合は、データが正しくない場合に例外をスローします。
- 変換:入力データを目的の形式(例:文字列から整数)に変換します。
NestJSバリデーションパイプは、ルートに渡された引数をチェックします。引数が有効な場合、パイプは引数を変更せずにルートハンドラーに渡します。ただし、引数が指定されたバリデーションルールのいずれかに違反している場合、パイプは例外をスローします。
次の2つの図は、任意の/example
ルートのバリデーションパイプの動作を示しています。
このセクションでは、バリデーションのユースケースに焦点を当てます。
ValidationPipe
をグローバルに設定
インプットバリデーションを実行するには、組み込みのNestJS ValidationPipe
を使用します。ValidationPipe
は、受信クライアントペイロードすべてにバリデーションルールを適用するための便利なアプローチを提供します。バリデーションルールは、class-validator
パッケージのデコレーターで宣言されます。
この機能を使用するには、プロジェクトに2つのパッケージを追加する必要があります。
class-validatorパッケージは、入力データを検証するためのデコレーターを提供し、class-transformerパッケージは、入力データを目的の形式に変換するためのデコレーターを提供します。どちらのパッケージもNestJSパイプとよく統合されています。
次に、ValidationPipe
をmain.ts
ファイルにインポートし、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
エンドポイントに送信してみてください。
このようにして、無効な値を挿入できます。ここでは、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ニュースレターにサインアップ