NestJS Path Variable 숫자 검증 오류 해결
Issue
섹션 제목: “Issue”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에 실패했다.
Cause
섹션 제목: “Cause”Path parameter는 기본적으로 문자열로 들어온다.
Path Variable이나 Query String은 URL을 통해 전달되기 때문에, NestJS에 들어올 때는 기본적으로 문자열이다.
예를 들어 아래 요청에서:
https://www.example.com/api/v1/order/prod/count/ABCD321321-12/3count는 실제로는 숫자 3이 아니라 문자열 '3'으로 들어온다.
{ id: 'ABCD321321-12', count: '3'}이 상태에서 @IsNumber()가 바로 적용되면 문자열 '3'은 number가 아니기 때문에 검증에 실패한다.
여기서 중요한 전제는 NestJS에서 ValidationPipe의 transform 옵션이 활성화되어 있어야 DTO 변환이 일어난다는 점이다. 이번 글은 그 전제 위에서 class-transformer를 사용해 문자열을 number로 바꾸는 케이스를 다룬다.
Resolution
섹션 제목: “Resolution”
@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 기반 입력에서는 더 쉽게 드러난다.
Notes
섹션 제목: “Notes”1) Path Variable과 Query String은 기본적으로 문자열이다
섹션 제목: “1) Path Variable과 Query String은 기본적으로 문자열이다”- 숫자처럼 보여도 서버에 처음 들어올 때는 문자열일 수 있다.
- DTO 타입 선언만으로는 자동 숫자 변환이 보장되지 않는다.
2) @Type(() => Number)만으로 끝나지 않을 수 있다
섹션 제목: “2) @Type(() => Number)만으로 끝나지 않을 수 있다”- NestJS에서 DTO 변환이 실제로 일어나려면
ValidationPipe의transform옵션도 같이 확인해야 한다. - 변환이 꺼져 있으면
class-transformer를 기대한 대로 쓰기 어렵다.
3) 같이 확인할 것
섹션 제목: “3) 같이 확인할 것”ValidationPipe({ transform: true })가 활성화되어 있는지ValidationPipe는 보통 아래처럼 전역으로 설정할 수 있다.-
app.useGlobalPipes(new ValidationPipe({transform: true,}),);
@Type(() => Number)가 필요한 필드에 적용돼 있는지- Path Variable / Query String 값을 DTO로 받고 있는지
- 숫자 검증을
@IsNumber()로 하고 있는지