アレについて記す

Nest(TypeScript)で遊んでみる 〜Validation編〜

Posted on September 13, 2018 at 22:45 (JST)

今回はNestにて入力値チェックを行う方法について記載する。

  1. Validationの概要
  2. ValidationPipe導入
  3. ValidationPipeのオプション

作成したコード [ nest-angular-sample: works/06_validation ]

動作環境

OS: macOS High Sierra ver. 10.13.4
Nodejs: v8.10.0
npm: 5.6.0
nest(core): 5.3.6

Validationの概要

Nestでは Pipes と呼ばれるデコレータにより、Request値をControllerで受け取るまえに任意の処理を挟み込める。
あらかじめ用意されている ValidationPipe を有効にすることで、入力値チェックや特定の型への変換が可能となる。

入力チェックには class-validator 、型変換には class-transformer が利用されている。
使用方法は各プロジェクトのReadmeが参考になる。

以降、導入手順を記載していく。

1. ライブラリのインストール

必要となるライブラリをインストールする。

$ npm install --save class-validator class-transformer

2. ValidationPipe有効化

全てのエンドポイントに対して有効にしたい場合は、app#useGlobalPipesValidationPipe を設定する。

[src/app.factory.ts]

import { ValidationPipe } from '@nestjs/common';

export async function initApp() {
  const app = await NestFactory.create(AppModule, {
    logger: false,
  });
  app.useLogger(app.get(Logger));
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.HTTP_PORT || 3000);
  return app;
}

一部のエンドポイントのみに適用したい場合は UsePipes デコレータにて設定する。

@Post()
@UsePipes(new ValidationPipe())
async create(@Body() dto: SomeDto) {
  // do something...
}

3. チェック用デコレータ指定

DTOのフィールドにデコレータを付与する。
使用できるデコレータの一覧はclass-validatorのReadmeに記載されている。

[src/tasks/dto/task.dto.ts]

import { IsString, IsNotEmpty, IsDefined, IsInt, IsPositive, MaxLength } from 'class-validator';

export class CreateTaskDto {
  @IsString()
  @IsNotEmpty()
  @MaxLength(256)
  readonly overview: string;

  @IsInt()
  @IsPositive()
  @IsDefined()
  readonly priority: number;

  @IsString()
  @IsDefined()
  readonly deadLine: Date;
}

アプリケーション起動/検証

リクエスト: POST http://localhost:3000/tasks

{ "task": {"priority":-3,"deadLine":"2018-09-10T08:55:28.087Z"}}

レスポンス

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": [
        {
            "target": {
                "priority": -3,
                "deadLine": "2018-09-10T08:55:28.087Z"
            },
            "property": "overview",
            "children": [],
            "constraints": {
                "maxLength": "overview must be shorter than or equal to 256 characters",
                "isNotEmpty": "overview should not be empty",
                "isString": "overview must be a string"
            }
        },
        {
            "target": {
                "priority": -3,
                "deadLine": "2018-09-10T08:55:28.087Z"
            },
            "value": -3,
            "property": "priority",
            "children": [],
            "constraints": {
                "isPositive": "priority must be a positive number"
            }
        }
    ]
}

target は処理対象となったRequestObjectとなる。

ValidationPipeのオプション

下記のようにオプションを指定することができる。

app.useGlobalPipes(new ValidationPipe({
  disableErrorMessages: true,
  transform: true
}));

・disableErrorMessages

エラーメッセージ(詳細)を出力しないための設定。
statusCodeと、そのコードの説明レベルのメッセージのみの出力になる。

{
    "statusCode": 400,
    "error": "Bad Request"
}

・whitelist

DTOに宣言されているプロパティだけを後続処理に渡すことができる。

例) DTOに存在しない status を含んだRequestを下記createメソッドに投げた場合

{ "task": {"priority":-3,"deadLine":"2018-09-10T08:55:28.087Z", "status":"done"}}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body('task') dto: CreateTaskDto) {
  this.logger.debug('create: ' + JSON.stringify(dto));

  const created = await this.tasksService.create(dto);
  return { task: created };
}

whitelist: false (or 未指定)の場合のログ。 status がdtoに代入されている。

2018-09-13T22:01:43.286+0900 [DEBUG] create: {"overview":"Learn Nest","priority":1,"deadLine":"2018-09-10T08:55:28.087Z","status":"done"}

whitelist: true の場合のログ。 status はdtoに代入されていない。

018-09-13T21:56:27.971+0900 [DEBUG] create: {"overview":"Learn Nest","priority":1,"deadLine":"2018-09-10T08:55:28.087Z"}

・forbidNonWhitelisted

DTOに宣言されていないプロパティが渡された場合、エラーとすることができる。
whitelist: true を指定しなと有効にならない点に注意が必要。

エラー時のレスポンスは下記となる。

{
    "statusCode": 400,
    "error": "Bad Request",
    "message": [
        {
            "target": {
                "overview": "Learn Nest",
                "priority": 1,
                "deadLine": "2018-09-10T08:55:28.087Z",
                "status": "done"
            },
            "property": "status",
            "value": "done",
            "constraints": {
                "whitelistValidation": "property status should not exist"
            }
        }
    ]
}

・transform

DTOのClassに変換することができる。
このオプションを使用する場合は事前に class-transformer のインストールが必要。

transform: false (or 未指定)の場合の console.log(dto) の結果。
dtoの実態はただのobject

{ overview: 'Learn Nest',
  priority: 1,
  deadLine: '2018-09-10T08:55:28.087Z' }

transform: true の場合。
dtoは指定したDto(Class)のインスタンスとなる。

CreateTaskDto {
  overview: 'Learn Nest',
  priority: 1,
  deadLine: '2018-09-10T08:55:28.087Z' }

ただし、値自体はキャストされていない

上記の例の場合、deadLineはDateには変換されずに文字列のままである。
dto.deadLine.getFullYear() を実行すると、dto.deadLine.getFullYear is not a function エラーとなる。


以上。


参考URL