[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
助かりました。ありがとうございます!!