Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Pipes | NestJS 【翻訳】

$
0
0

NestJS公式ドキュメント翻訳

NestJS公式ドキュメント翻訳

原文

Pipes| NestJS - A progressive Node.js web framework

パイプ

パイプは@Injectable()デコレータが付けられたクラスです。パイプはPipeTransformインターフェースを実装する必要があります。

キャプチャ.PNG

パイプには2つの典型的な使用例があります。

  • 変換:入力データを目的の出力に変換します
  • バリデーション:入力データを評価し、有効であればそのまま変更せずに渡します。データが正しくないときは例外をスローします。

どちらの場合も、パイプはコントローラルートハンドラーによって処理されているargumentsで動作します。Nestはメソッドが呼び出される直前にパイプを挿入し、パイプはメソッド宛の引数を受け取ります。その時点で変換またはバリデートが行われた後、ルートハンドラーが(潜在的に)変換された引数で呼び出されます。

パイプは例外ゾーン内で実行されます。これはパイプが例外をスローすると例外レイヤー(グローバル例外フィルターと現在のコンテキストに適用される例外フィルター)によって処理されることを意味します。つまり、Pipeで例外がスローされるとその後コントローラメソッドは実行されません。

組み込みパイプ

NestにはValidationPipeParseIntPipeParseUUIDPipeの3つのパイプがあり、すぐに使用することができます。@nestjs/commonパッケージからエクスポートされます。これらがどのように機能するかをよりよく理解するために、それらをゼロから構築してみましょう。

ValidationPipeから始めましょう。まずは、単純に入力値を取得しすぐに同じ値を返すようにします。

validation.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata}from'@nestjs/common';@Injectable()exportclassValidationPipeimplementsPipeTransform{transform(value:any,metadata:ArgumentMetadata){returnvalue;}}

PipeTransform<T, R>は汎用インターフェイスで、Tは入力値の型を、Rtransform()メソッドの戻り値の型を示します。

すべてのパイプはtransform()メソッドを提供する必要があります。このメソッドには2つのパラメーターがあります。

  • value
  • metadata

valueは現在(ルートハンドラーメソッドによって受信される前)処理されている引数であり、metadataはそのメタデータです。メタデータオブジェクトには次のプロパティがあります。

exportinterfaceArgumentMetadata{type:'body'|'query'|'param'|'custom';metatype?:Type<any>;data?:string;}

これらのプロパティは現在処理されている引数を記述します。

type引数がbody@Body()、query@Query()、param @Param()、カスタムパラメーター(詳細はこちら)のどれかを示します。
metatype引数のメタタイプ(Stringなど)を提供します。※ ルートハンドラーメソッドシグネチャで型宣言を省略するか、バニラJavaScriptを使用すると値はundefinedになります。
data@Body('string')などデコレータに渡される文字列。デコレータの括弧を空のままにするとundefinedになります。

TypeScriptインターフェースはトランスパイル時に消えます。したがって、メソッドパラメーターの型がクラスではなくインターフェースとして宣言されている場合、metatypeの値はObjectになります。

バリデーションのユースケース

CatsControllercreate()メソッドを詳しく見てみましょう。

@Post()asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

createCatDtobodyパラメーターに注目しましょう。型はCreateCatDtoです。

create-cat.dto.ts
exportclassCreateCatDto{readonlyname:string;readonlyage:number;readonlybreed:string;}

createメソッドへのリクエストには有効なボディが含まれるようにします。そのため、createCatDtoオブジェクトの3つのメンバーをバリデートする必要があります。ルートハンドラーメソッド内でこれを実行することもできますが、単一責任原則(SRP)を破ります。別のアプローチとして、バリデータクラスを作成しそこにタスクを委任することもできますが、各メソッドの最初にこのバリデータを使用する必要があります。それではバリデートミドルウェアを作るのはどうでしょうか?これは良いアイデアかもしれませんが、アプリケーション全体で使用できる汎用ミドルウェアを作成することはできません(ミドルウェアは呼び出されるハンドラーとそのパラメーターを含む実行コンテキストを認識しないため)。

パイプは最適なケースです。それでは1つを作成しましょう。

オブジェクトスキーマバリデーション

オブジェクトのバリデーションにはいくつかのアプローチがあります。一般的なアプローチの1つは、スキーマベースのバリデーションを使用することです。Joiライブラリを使用すると、読み取り可能なAPIを使用して非常に簡単な方法でスキーマを作成できます。Joiベースのスキーマを利用するパイプを見てみましょう。

最初に必要なパッケージをインストールします。

$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi

以下のサンプルコードでは、​​constructor引数としてスキーマを使用する単純なクラスを作成します。次にschema.validate()メソッドを適用します。このメソッドは指定されたスキーマに対して引数を検証します。

前述のように、バリデーションパイプは値を変更せずに返すか例外をスローします。

次のセクションでは、@UsePipes()デコレータを使用して特定のコントローラメソッドに適切なスキーマを提供する方法を説明します。

import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';@Injectable()exportclassJoiValidationPipeimplementsPipeTransform{constructor(privatereadonlyschema:Object){}transform(value:any,metadata:ArgumentMetadata){const{error}=this.schema.validate(value);if(error){thrownewBadRequestException('Validation failed');}returnvalue;}}

パイプのバインディング

パイプのバインディング(適切なコントローラまたはハンドラーへのパイプ処理)は非常に簡単です。@UsePipes()デコレータを使用してパイプインスタンスを作成し、Joiバリデーションスキーマを渡します。

@Post()@UsePipes(newJoiValidationPipe(createCatSchema))asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

Class validator

このセクションの手法にはTypeScriptが必要であり、アプリがバニラJavaScriptを使用して記述されている場合は利用できません。

バリデーション方法の代替実装を見てみましょう。

Nestはclass-validatorライブラリと連携して動作します。このすばらしいライブラリを使うと、デコレータベースのバリデーションを行うことができます。処理されたプロパティのメタタイプにアクセスできるため、デコレータベースのバリデーションは特にNestのパイプ機能と組み合わせると非常に強力です。使用前に必要なパッケージをインストールする必要があります。

$ npm i --save class-validator class-transformer

インストールできたらCreateCatDtoクラスにいくつかのデコレータを追加します。

create-cat.dto.ts
import{IsString,IsInt}from'class-validator';exportclassCreateCatDto{@IsString()readonlyname:string;@IsInt()readonlyage:number;@IsString()readonlybreed:string;}

class-validatorデコレータの詳細については、こちらをご覧ください。

これでValidationPipeクラスを作成できます。

validation.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';import{validate}from'class-validator';import{plainToClass}from'class-transformer';@Injectable()exportclassValidationPipeimplementsPipeTransform<any>{asynctransform(value:any,{metatype}:ArgumentMetadata){if(!metatype||!this.toValidate(metatype)){returnvalue;}constobject=plainToClass(metatype,value);consterrors=awaitvalidate(object);if(errors.length>0){thrownewBadRequestException('Validation failed');}returnvalue;}privatetoValidate(metatype:Function):boolean{consttypes:Function[]=[String,Boolean,Number,Array,Object];return!types.includes(metatype);}}

上記ではclass-transformerライブラリを使用しました。class-validatorライブラリと同じ作成者が作成しており非常にうまく機能します。

このコードを見ていきましょう。まず、transform()関数がasyncであることに注意してください。これは、Nestは同期パイプと非同期パイプの両方をサポートしており、またclass-validatorのバリデートの一部が非同期(Promiseを利用)にできるためです。

次に、構造化することでmetatypeパラメーターとしてメタタイプフィールドを取り出します(ArgumentMetadataからこのメンバーだけを抽出します)。これは完全なArgumentMetadataを取得し、メタタイプ変数を割り当てるための追加ステートメントを使用するための略記です。

次に、ヘルパー関数toValidate()を見てください。処理中の現在の引数がネイティブJavaScript型である場合、バリデーションステップをパスする責任があります(これらはスキーマをアタッチできないためバリデーションステップを実行する理由はありません)。

次に、class-transformer関数plainToClass()を使用して、プレーンJavaScript引数オブジェクトを型付きオブジェクトに変換しバリデーションを適用できるようにします。ネットワークリクエストからデシリアライズされる際、ボディは型情報を持ちません。class-validatorは、以前にDTOに対して定義したバリデーションデコレータを使用する必要があるため、この変換を実行する必要があります。

そして前述のとおり、これはバリデーションパイプであるため値を変更せずに返すか例外をスローします。

最後のステップとしてValidationPipeをバインドします。例外フィルターと同様に、パイプはメソッドスコープ、コントローラスコープ、またはグローバルスコープにすることができます。さらに、パイプはパラメータースコープにすることもできます。以下の例では、パイプインスタンスをルートパラメーター@Body()デコレータに直接結び付けます。

cats.controller.ts
@Post()asynccreate(@Body(newValidationPipe())createCatDto:CreateCatDto,){this.catsService.create(createCatDto);}

パラメータスコープのパイプは、バリデーションロジックが1つの特定のパラメーターのみに関係する場合に役立ちます。

または、メソッドレベルでパイプを設定するには@UsePipes()デコレータを使用します。

cats.controller.ts
@Post()@UsePipes(newValidationPipe())asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

@UsePipes()デコレータは@nestjs/commonパッケージからインポートされます。

上記の例では、ValidationPipeのインスタンスがすぐに作成されています。または、(インスタンスではなく)クラスを渡すことでインスタンス化をフレームワークに任せ、依存性注入を有効にします。

cats.controller.ts
@Post()@UsePipes(ValidationPipe)asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

ValidationPipeは非常に汎用的に作成されているため、アプリケーション全体のすべてのルートハンドラーに適用されるグローバルスコープのパイプとして設定してみましょう。

main.ts
asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.useGlobalPipes(newValidationPipe());awaitapp.listen(3000);}bootstrap();

ハイブリッドアプリの場合、useGlobalPipes()メソッドはゲートウェイおよびマイクロサービス用のパイプを設定しません。 「スタンダード(非ハイブリッド)」マイクロサービスアプリの場合、useGlobalPipes()はパイプをグローバルにマウントします。

グローバルパイプは、すべてのコントローラとルートハンドラーに対してアプリケーション全体で使用されます。依存性注入に関しては、モジュールの外部で登録されたグローバルパイプ(上記の例のようなuseGlobalPipes())は、モジュールのコンテキスト外で行われるため依存性を注入できません。次のようにすることで、任意のモジュールからグローバルパイプを直接設定しこの問題を解決することができます。

app.module.ts
import{Module}from'@nestjs/common';import{APP_PIPE}from'@nestjs/core';@Module({providers:[{provide:APP_PIPE,useClass:ValidationPipe,},],})exportclassAppModule{}

このアプローチでパイプの依存性注入を実行する場合、この構造が使用されるモジュールに関係なく、パイプは実際にはグローバルであることに注意してください。これはどこで行われるのでしょうか?パイプ(上記の例ではValidationPipe)が定義されているモジュールを選択します。また、カスタムプロバイダの登録を処理する方法はuseClassだけではありません。詳細はこちらをご覧ください。

変換のユースケース

パイプのユースケースはバリデーションだけではありません。この章の冒頭で、パイプを使用して入力データを目的の出力に変換できることを説明しました。これはtransform関数から返された値が引数の以前の値を完全にオーバーライドするため実現することができます。どのような場面で役に立つでしょうか?クライアントから渡されたデータはルートハンドラーメソッドによって適切に処理される前に、文字列を整数に変換するなど何らかの変更が必要になることがあります。さらに、一部の必須データフィールドが欠落している可能性があるため、デフォルト値を適用したい場合もあるでしょう。変換パイプはクライアントリクエストとリクエストハンドラの間に処理機能を挿入することにより、これらの機能を実行できます。

これは文字列を整数値に解析するためのParseIntPipeです。

parse-int.pipe.ts
import{PipeTransform,Injectable,ArgumentMetadata,BadRequestException}from'@nestjs/common';@Injectable()exportclassParseIntPipeimplementsPipeTransform<string,number>{transform(value:string,metadata:ArgumentMetadata):number{constval=parseInt(value,10);if(isNaN(val)){thrownewBadRequestException('Validation failed');}returnval;}}

以下のように、このパイプを選択したパラメーターに簡単に結びつけることができます。

@Get(':id')asyncfindOne(@Param('id',newParseIntPipe())id){returnawaitthis.catsService.findOne(id);}

必要に応じて、文字列のパースを担当するParseUUIDPipeを使用し、UUIDかどうかを検証することができます。

@Get(':id')asyncfindOne(@Param('id',newParseUUIDPipe())id){returnawaitthis.catsService.findOne(id);}

ParseUUIDPipe()を使用する場合、バージョン3,4,5でUUIDをパースしています。特定のバージョンのUUIDのみが必要な場合は、パイプオプションでバージョンを渡すことができます。

これを設定すると、リクエストが対応するハンドラーに到達する前にParseIntPipeまたはParseUUIDPipeが実行され、idパラメーターの整数またはUUIDを常に受け​​取るようになります。

別の便利なケースは、IDによってデータベースから既存のユーザーエンティティを選択するケースです。

@Get(':id')findOne(@Param('id',UserByIdPipe)userEntity:UserEntity){returnuserEntity;}

このパイプの実装は読者に任せます。他のすべての変換パイプと同様に、入力値(id)を受け取り、出力値(UserEntityオブジェクト)を返すことに注意してください。ハンドラーから共通パイプへボイラープレートコードを抽象化することにより、コードをより宣言的でDRYにすることができます。

組み込みのValidationPipe

幸いなことに、ValidationPipeParseIntPipeはNestで提供されているため、これらのパイプを自分で作成する必要はありません。 (ValidationPipeではclass-validatorclass-transformerパッケージの両方をインストールする必要があることに注意してください)。

組み込みのValidationPipeは、この章で作成したサンプルよりも多くのオプションを提供しています。サンプルではパイプの基本的な仕組みを説明しました。ここでさらに多くの例を見ることができます。

そのようなオプションの1つがtransformです。シリアル化されていないbodyオブジェクトがバニラJavaScriptオブジェクトである(DTOタイプを持たない)という以前の議論を思い出してください。これまで、パイプを使用してペイロードを検証してきました。バリデーションができるように、その過程でclass-transformを使用してプレーンオブジェクトを型付きオブジェクトに一時的に変換したことを思い出してください。組み込みのValidationPipeはオプションでこの変換されたオブジェクトを返すこともできます。構成オブジェクトをパイプに渡すことにより、この動作を有効にします。このオプションの場合、以下に示すように、値がtrueのフィールドtransformを持つ構成オブジェクトを渡します。

cats.controller.ts
@Post()@UsePipes(newValidationPipe({transform:true}))asynccreate(@Body()createCatDto:CreateCatDto){this.catsService.create(createCatDto);}

ValidationPipe@nestjs/commonパッケージからインポートされます。

このパイプはclass-validatorライブラリとclass-transformerライブラリに基づいているため、多くの追加オプションが利用可能です。上記のtransformオプションと同様に、パイプに渡された構成オブジェクトを介してこれらの設定を構成します。組み込みオプションは次のとおりです。

exportinterfaceValidationPipeOptionsextendsValidatorOptions{transform?:boolean;disableErrorMessages?:boolean;exceptionFactory?:(errors:ValidationError[])=>any;}

これらに加えて、すべてのclass-validatorオプション(ValidatorOptionsインターフェイスから継承)が利用可能です:

オプション説明
skipMissingPropertiesbooleantrueに設定すると、バリデータはバリデーションオブジェクトにないすべてのプロパティのバリデーションをスキップします。
whitelistbooleantrueに設定すると、バリデータはバリデーションデコレータを使用しないプロパティの検証済み(返された)オブジェクトを取り除きます。
forbidNonWhitelistedbooleantrueに設定すると、ホワイトリストに登録されていないプロパティバリデータを削除する代わりに例外がスローされます。
forbidUnknownValuesbooleantrueに設定すると、不明なオブジェクトをバリデートしようとするとすぐに失敗します。
disableErrorMessagesbooleantrueに設定すると、バリデーションエラーはクライアントに返されません。
exceptionFactoryFunctionバリデーションエラーの配列を受け取り、スローされる例外オブジェクトを返します。
groupsstring[]オブジェクトのバリデーション中に使用されるグループです。
dismissDefaultMessagesbooleantrueに設定されている場合、バリデーションではデフォルトのメッセージは使用されません。明示的に設定されていない場合エラーメッセージは常にundefinedになります。
validationError.targetbooleanValidationErrorで対象を公開するかどうかを示します。
validationError.valuebooleanValidationErrorでバリデーション済みの値を公開するかどうかを示します。

class-validatorパッケージの詳細についてはリポジトリをご覧ください。


Viewing all articles
Browse latest Browse all 8691

Trending Articles