Pipes | NestJS 【翻訳】

## NestJS公式ドキュメント翻訳

[NestJS公式ドキュメント翻訳](https://qiita.com/mana-vv/items/a658051a5183183c3e50)

## 原文

[Pipes| NestJS - A progressive Node.js web framework](https://docs.nestjs.com/pipes)

## パイプ

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

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499074/a282671d-9e86-4b8d-984b-95693bc17790.png)


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

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

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

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

### 組み込みパイプ

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

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

```typescript:validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
```

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

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

- `value`
- `metadata`

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

```typescript
export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<any>;
  data?: string;
}
```

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

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

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

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

`CatsController`の`create()`メソッドを詳しく見てみましょう。

```typescript
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

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


```typescript:create-cat.dto.ts
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}
```

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

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

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

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

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

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

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

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

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

```typescript
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private readonly schema: Object) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = this.schema.validate(value);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}
```

### パイプのバインディング

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

```typescript
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

### Class validator

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

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

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

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

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

```typescript:create-cat.dto.ts
import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}
```

class-validatorデコレータの詳細については、[こちら](https://github.com/typestack/class-validator#usage)をご覧ください。

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

```typescript:validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}
```

上記では[class-transformer](https://github.com/typestack/class-transformer)ライブラリを使用しました。class-validatorライブラリと同じ作成者が作成しており非常にうまく機能します。

このコードを見ていきましょう。まず、`transform()`関数が`async`であることに注意してください。これは、Nestは同期パイプと**非同期**パイプの両方をサポートしており、またclass-validatorのバリデートの一部が[非同期](https://github.com/typestack/class-validator#custom-validation-classes)（Promiseを利用）にできるためです。

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

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

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

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

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

```typescript:cats.controller.ts
@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}
```

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

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

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

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

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

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

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

```typescript:main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();
```

[ハイブリッドアプリ](https://docs.nestjs.com/faq/hybrid-application)の場合、`useGlobalPipes()`メソッドはゲートウェイおよびマイクロサービス用のパイプを設定しません。 「スタンダード（非ハイブリッド）」マイクロサービスアプリの場合、`useGlobalPipes()`はパイプをグローバルにマウントします。

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

```typescript:app.module.ts
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}
```

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

### 変換のユースケース

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

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

```typescript:parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
```

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

```typescript
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}
```

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

```typescript
@Get(':id')
async findOne(@Param('id', new ParseUUIDPipe()) id) {
  return await this.catsService.findOne(id);
}
```

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

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

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

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

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

### 組み込みのValidationPipe

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

組み込みの`ValidationPipe`は、この章で作成したサンプルよりも多くのオプションを提供しています。サンプルではパイプの基本的な仕組みを説明しました。[ここ](https://docs.nestjs.com/techniques/validation)でさらに多くの例を見ることができます。

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

```typescript:cats.controller.ts
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

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

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

```typescript
export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}
```

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

|オプション |型 | 説明|
|-----------|:-------------|---|
|`skipMissingProperties`|`boolean`|trueに設定すると、バリデータはバリデーションオブジェクトにないすべてのプロパティのバリデーションをスキップします。|
|`whitelist`|`boolean`|trueに設定すると、バリデータはバリデーションデコレータを使用しないプロパティの検証済み（返された）オブジェクトを取り除きます。|
|`forbidNonWhitelisted`|`boolean`|trueに設定すると、ホワイトリストに登録されていないプロパティバリデータを削除する代わりに例外がスローされます。|
|`forbidUnknownValues`|`boolean`|trueに設定すると、不明なオブジェクトをバリデートしようとするとすぐに失敗します。|
|`disableErrorMessages`|`boolean`|trueに設定すると、バリデーションエラーはクライアントに返されません。|
|`exceptionFactory`|`Function`|バリデーションエラーの配列を受け取り、スローされる例外オブジェクトを返します。|
|`groups`|`string[]`|オブジェクトのバリデーション中に使用されるグループです。|
|`dismissDefaultMessages`|`boolean`|trueに設定されている場合、バリデーションではデフォルトのメッセージは使用されません。明示的に設定されていない場合エラーメッセージは常に`undefined`になります。|
|`validationError.target`|`boolean`|`ValidationError`で対象を公開するかどうかを示します。|
|`validationError.value`|`boolean`|`ValidationError`でバリデーション済みの値を公開するかどうかを示します。|

`class-validator`パッケージの詳細については[リポジトリ](https://github.com/typestack/class-validator)をご覧ください。