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
エラーとなる。
以上。