[NestJS] ValidationPipeとDTOを使った数字のリクエストパラメータの型変換とバリデーション
リクエストのパラメータが文字列として取得される問題
- http://localhost:4000/?page=1 でリクエストすると、pageの値が数値ではなく文字列として取得される。
以下のようにControllerとDTOを用意します。
import { Controller, Get, Query } from '@nestjs/common';
import { HelloDto } from './dto/hello-dto';
@Controller()
export class AppController {
@Get()
getHello(@Query() helloDto: HelloDto) {
console.log('helloDto', helloDto);
return {
message: 'hello',
};
}
}
export class HelloDto {
page: number;
}
リクエストすると出力は以下のようにpageが文字列となります。
helloDto { page: '1' }
また、以下のようにValidationPipe, class-validatorを使ってバリデーションをすると、pageパラメータに数値を指定しているのにバリデーションエラーが発生してしまう。
import { Controller, Get, Query, ValidationPipe } from '@nestjs/common';
import { HelloDto } from './dto/hello-dto';
@Controller()
export class AppController {
@Get()
getHello(@Query(new ValidationPipe()) helloDto: HelloDto) {
console.log('helloDto', helloDto);
return {
message: 'hello',
};
}
}
import { IsInt } from 'class-validator';
export class HelloDto {
@IsInt()
page: number;
}
リクエストするとバリデーションエラーのレスポンスとなる。
{"message":["page must be an integer number"],"error":"Bad Request","statusCode":400}
これは以下のために起こります。
- リクエストパラメータは型情報を持たず文字列として受け取られる
-
ValidationPipeによるバリデーション前に、DTOへの変換が行われるがこの段階で型変換はされない-
validation.pipeのここで 変換されます https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L124-L128
-
http://localhost:3000?page=1としてもpageは"1"となりstringとなります。
そして、DTOクラスに変換されてもnumberにはならずにstringのままです。
そのためIsInt()に反してエラーになります。
バリデーションエラー解決策
以下の2つが考えられます。
- 暗黙的に型変換
- 明示的に型変換
暗黙的に型変換
enableImplicitConversionオプションを使います。
import { Controller, Get, Query, ValidationPipe } from '@nestjs/common';
import { HelloDto } from './dto/hello-dto';
@Controller()
export class AppController {
@Get()
getHello(
@Query(
new ValidationPipe({
transformOptions: { enableImplicitConversion: true },
}),
)
helloDto: HelloDto,
) {
console.log('helloDto', helloDto);
return {
message: 'hello',
};
}
}
これにより、ValidationPipe内以下のthis.transformOptionsでenableImplicitConversionオプションが渡され、型変換されます。
pageがnumberとなるのでバリデーションが正常に行われるようになります。
明示的に型変換
明示的に変換するには、Transform()デコレータを使います。
import { Transform } from 'class-transformer';
import { IsInt } from 'class-validator';
export class HelloDto {
@Transform(({ value }) => parseInt(value))
@IsInt()
page: number;
}
DTOに上記のように@Transform(({ value }) => parseInt(value))で変換処理をいれておきます。
これにより、以下のentityは型変換されたものになるのでバリデーションが期待通りに動作するようになります。
明示的にした方が意図が明確になるので分かりやすいかもしれませんね。
pageの値が数値ではなく文字列として取得される件の解決策
ValidationPipeのtransformオプションを使用します。
import { Controller, Get, Query, ValidationPipe } from '@nestjs/common';
import { HelloDto } from './dto/hello-dto';
@Controller()
export class AppController {
@Get()
getHello(@Query(new ValidationPipe({ transform: true })) helloDto: HelloDto) {
console.log('helloDto', helloDto);
return {
message: 'hello',
};
}
}
DTOはTransformしておくこと。
import { Transform } from 'class-transformer';
import { IsInt } from 'class-validator';
export class HelloDto {
@Transform(({ value }) => parseInt(value))
@IsInt()
page: number;
}
これにより、出力は以下となります。
helloDto HelloDto { page: 1 }
transformオプションを使用していない時は、単なるオブジェクトでしたが、オプションを使用するとDTOのインスタンスとなります。
pageも型変換され数値となっています。
これは、ValidationPipeの以下の箇所で、型変換されたDTOのインスタンスが
以下で返却されているためです。
transformオプションがtrueでないと、this.isTransformEnabledがfalseとなり、以下のところでvalue({page: '1'})がそのまま返却されます。
最後に
サンプルコードはGitHubにあげています。
この記事が少しでも参考になればいいねを押してもらえれば励みになります!
最後まで読んでいただきありがとうございました。
Discussion
助かりました。ありがとうございます!!