Post

ValidationPipe 제대로 이해하기

ValidationPipe 제대로 이해하기

시작하기에 앞서

난 아직 ValidationPipe에서 transform 옵션만 켜둔 상태였기 때문에
보안 관련 옵션인 whitelist와 forbidNonWhitelisted를 적용하려 한다!



1. ValidationPipe 전역 설정하기

Nest.js에서는 main.ts에서 ValidationPipe를 전역으로 적용할 수 있다.
이렇게 하면 모든 엔드포인트에 자동으로 유효성 검증이 걸려서 컨트롤러마다 따로 처리할 필요가 없어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,             // DTO에 없는 속성 제거
      forbidNonWhitelisted: true,  // 허용되지 않은 속성 있으면 에러 발생
      transform: true,             // 요청 값을 DTO 타입에 맞게 변환
    }),
  );

  await app.listen(3000);
}
bootstrap();



2. 주요 옵션 설명

transform: true

  • 요청으로 들어온 값을 DTO에 정의된 타입으로 자동 변환
  • 예: "20"(string) → 20(number)

whitelist: true

  • DTO에 정의되지 않은 속성은 자동으로 제거
  • 불필요한 데이터가 들어와도 무시됨

forbidNonWhitelisted: true

  • DTO에 없는 속성이 들어오면 에러를 발생
  • whitelist보다 더 엄격한 옵션

disableErrorMessages: true

(선택)

  • 에러 메시지 비활성화
  • 프로덕션 환경에서 민감한 정보 노출 방지를 위해 사용

3. DTO와 Validation 예시

예시 DTO 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { IsString, IsInt, MinLength, Min } from 'class-validator';

export class CreateRestaurantDto {
  @IsString()
  @MinLength(2)
  name: string;

  @IsString()
  address: string;

  @IsInt()
  @Min(1)
  seatCount: number;
}

컨트롤러에 적용

1
2
3
4
5
6
7
8
9
10
import { Body, Controller, Post } from '@nestjs/common';
import { CreateRestaurantDto } from './restaurant.dto';

@Controller('restaurants')
export class RestaurantController {
  @Post()
  create(@Body() data: CreateRestaurantDto) {
    return data;
  }
}

잘못된 요청 예시

1
2
3
4
5
6
{
  "name": "A",
  "address": "서울",
  "seatCount": "0",
  "extra": true
}

응답 (400 에러)

1
2
3
4
5
6
7
8
9
{
  "statusCode": 400,
  "message": [
    "name must be longer than or equal to 2 characters",
    "seatCount must not be less than 1",
    "property extra should not exist"
  ],
  "error": "Bad Request"
}
  • transform: true → seatCount가 문자열로 들어와도 숫자로 변환

  • whitelist: true → DTO에 없는 필드는 제거 대상이지만, forbidNonWhitelisted가 true이면 에러 발생

  • forbidNonWhitelisted: true → extra 필드 때문에 에러 발생

  • class-validator → name과 seatCount는 각각 @MinLength(), @Min 유효성 검사에 의해 에러 발생



결론

  • ValidationPipe는 전역으로 적용해두면 가장 안전하다.

  • 커스텀 검증기(validator)를 활용하면 서비스에 맞는 맞춤형 유효성 검사도 가능하다.

  • 컨트롤러와 서비스는 핵심 비즈니스 로직에만 집중할 수 있게 된다.


This post is licensed under CC BY 4.0 by the author.