Nest(TypeScript)で遊んでみる 〜Validation編〜
Posted on September 13, 2018 at 22:45 (JST)
今回はNestにて入力値チェックを行う方法について記載する。
- Validationの概要
- ValidationPipe導入
- 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#useGlobalPipes に ValidationPipe を設定する。
[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 エラーとなる。
以上。