콘텐츠로 이동

NestJS Path Variable 숫자 검증 오류 해결

Path Variable의 숫자 값이 validation에서 number로 인식되지 않았다.

NestJS에서 주문 상품 수량을 변경하는 API를 만들던 중, count를 숫자로 검증하고 있었는데도 아래 에러가 발생했다.

count must be a number conforming to the specified constraints

예를 들어 아래와 같은 엔드포인트와 DTO가 있다고 가정한다.

@Patch('order/prod/count/:id/:count')
async updateOrdProd(@Param() param: OrderProdUpdateDto) {
return this.orderService.updateOrdProd(param.id, param.count);
}
export class OrderProdUpdateDto {
@IsString()
id: string;
@IsNumber()
count: number;
}

겉보기에는 count가 number 타입이고 @IsNumber()도 붙어 있으니 문제 없어 보이지만, 실제 요청에서는 validation에 실패했다.

Path parameter는 기본적으로 문자열로 들어온다.

Path Variable이나 Query String은 URL을 통해 전달되기 때문에, NestJS에 들어올 때는 기본적으로 문자열이다.

예를 들어 아래 요청에서:

https://www.example.com/api/v1/order/prod/count/ABCD321321-12/3

count는 실제로는 숫자 3이 아니라 문자열 '3'으로 들어온다.

{
id: 'ABCD321321-12',
count: '3'
}

이 상태에서 @IsNumber()가 바로 적용되면 문자열 '3'은 number가 아니기 때문에 검증에 실패한다.

여기서 중요한 전제는 NestJS에서 ValidationPipe의 transform 옵션이 활성화되어 있어야 DTO 변환이 일어난다는 점이다. 이번 글은 그 전제 위에서 class-transformer를 사용해 문자열을 number로 바꾸는 케이스를 다룬다.

@Type(() => Number)로 DTO 필드를 number로 변환한다.

해결 방법은 class-transformer@Type 데코레이터를 사용해 DTO 매핑 단계에서 문자열을 숫자로 변환하는 것이었다.

import { Type } from 'class-transformer';
import { IsNumber } from 'class-validator';
export class OrderProdUpdateDto {
@IsString()
id: string;
@Type(() => Number)
@IsNumber()
count: number;
}

이렇게 하면 count에 들어온 '3'이 DTO 변환 과정에서 3으로 바뀌고, 이후 @IsNumber() 검증도 통과할 수 있다.

이번 글에서 핵심은 데코레이터 줄 순서 자체보다, ValidationPipe의 transform과 class-transformer 변환이 먼저 일어나고 그 뒤에 class-validator 검증이 적용되는 흐름을 이해하는 것이었다.

즉 JSON Request Body에서는 잘 드러나지 않던 문제가, Path Variable이나 Query String처럼 URL 기반 입력에서는 더 쉽게 드러난다.


1) Path Variable과 Query String은 기본적으로 문자열이다

섹션 제목: “1) Path Variable과 Query String은 기본적으로 문자열이다”
  • 숫자처럼 보여도 서버에 처음 들어올 때는 문자열일 수 있다.
  • DTO 타입 선언만으로는 자동 숫자 변환이 보장되지 않는다.

2) @Type(() => Number)만으로 끝나지 않을 수 있다

섹션 제목: “2) @Type(() => Number)만으로 끝나지 않을 수 있다”
  • NestJS에서 DTO 변환이 실제로 일어나려면 ValidationPipetransform 옵션도 같이 확인해야 한다.
  • 변환이 꺼져 있으면 class-transformer를 기대한 대로 쓰기 어렵다.
  • ValidationPipe({ transform: true })가 활성화되어 있는지
    • ValidationPipe는 보통 아래처럼 전역으로 설정할 수 있다.
    • app.useGlobalPipes(
      new ValidationPipe({
      transform: true,
      }),
      );
  • @Type(() => Number)가 필요한 필드에 적용돼 있는지
  • Path Variable / Query String 값을 DTO로 받고 있는지
  • 숫자 검증을 @IsNumber()로 하고 있는지