[NestJS]バリデーションエラーをカスタマイズする

2024/02/14に公開

デフォルトのバリデーションエラーレスポンス

https://docs.nestjs.com/techniques/validation#using-the-built-in-validationpipe を参考にしつつ以下のコードを考えてみます。

src/app.controller.ts
import {
  Body,
  Controller,
  Post,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { AppService } from './app.service';
import { CreateUserDto } from './create-user-dto';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post()
  @UsePipes(new ValidationPipe())
  postHello(@Body() createUserDto: CreateUserDto) {
    console.log('createUserDto', createUserDto);
    return this.appService.getHello();
  }
}
src/create-user-dto.ts
import { IsEmail, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;
}

この状態でBodyを空でリクエストすると以下のレスポンスとなります。

{
    "message": [
        "email must be an email",
        "password should not be empty"
    ],
    "error": "Bad Request",
    "statusCode": 400
}

このレスポンスをカスタマイズしてみます。

カスタマイズ方法

ValidationPipeexceptionFactoryを使います。

src/app.controller.ts
import {
  BadRequestException,
  Body,
  Controller,
  Post,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { AppService } from './app.service';
import { CreateUserDto } from './create-user-dto';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Post()
  @UsePipes(
    new ValidationPipe({
      exceptionFactory: () => {
        return new BadRequestException({
          custom: 'message1',
          hoge: 'message2',
        });
      },
    }),
  )
  postHello(@Body() createUserDto: CreateUserDto) {
    console.log('createUserDto', createUserDto);
    return this.appService.getHello();
  }
}

するとレスポンスは以下のようにBadRequestExceptionのコンストラクタに渡したオブジェクトとなります。

{
    "custom": "message1",
    "hoge": "message2"
}

ちなみに、exceptionFactoryは引数を取ることもできます。

@UsePipes(
    new ValidationPipe({
      exceptionFactory: (error) => {
        console.log('error', error);
        return new BadRequestException(error);
      },
    }),
  )

今回の場合のerrorの中身は

[
        {
            "target": {},
            "property": "email",
            "children": [],
            "constraints": {
                "isEmail": "email must be an email"
            }
        },
        {
            "target": {},
            "property": "password",
            "children": [],
            "constraints": {
                "isNotEmpty": "password should not be empty"
            }
        }
    ],

このようになっているのでここからかいつまんでエラー内容をレスポンスに含めることもできます。

コードを追ってみる

ValidationPipeのコードは以下です。
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts

バリデーションエラーがあった場合は
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L142-L145
ここで例外を投げています。

this.exceptionFactory(errors);は何かというと、
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L78-L79
ValidationPipeのコンストラクタでセットしているものになります。

これが以下のようにオプションで設定したexceptionFactoryとなります。

 @UsePipes(
    new ValidationPipe({
      exceptionFactory: () => {
        return new BadRequestException({
          custom: 'message1',
          hoge: 'message2',
        });
      },
    }),
  )

ちなみに、オプションでexceptionFactoryをセットしなかった場合はデフォルトのthis.createExceptionFactory()となります。
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L167-L175

HttpErrorByCode[this.errorHttpStatusCode]というのは、
https://github.com/nestjs/nest/blob/d658942e1b8d6d214731297a36d014b330cf0aed/packages/common/utils/http-error-by-code.util.ts
ここで、HttpStatusごとにExceptionを割り当てているファイルがあり、

ValidationPipeの場合はthis.errorHttpStatusCodeはデフォルトでBAD_REQUESTとなるので、
https://github.com/nestjs/nest/blob/master/packages/common/pipes/validation.pipe.ts#L76

BadRequestExceptionを投げているということになります。

例外が発生した後は、NestJSの例外を拾うレイヤーでレスポンスが作られます。

https://github.com/nestjs/nest/blob/d658942e1b8d6d214731297a36d014b330cf0aed/packages/core/exceptions/base-exception-filter.ts#L33-L40

今回は以下のようにBadRequestExceptionにオブジェクトを渡しているため、

 @UsePipes(
    new ValidationPipe({
      exceptionFactory: () => {
        return new BadRequestException({
          custom: 'message1',
          hoge: 'message2',
        });
      },
    }),
  )

渡したオブジェクトがそのままレスポンスとなる形となっています。

サンプルプロジェクト

https://github.com/hid3h/nestjs-examples/tree/main/validation-message

おわりに

少しでも参考になれば幸いです。
よかったらいいね等お願いします!

Discussion