🔍 Intro: NestJS는 왜 DTO 유효성 검사가 중요한가?

NestJS는 객체지형, 함수형, 함수형 반응형 프레임워크를 결합한 의연성 주입 기반의 Node.js 백엔드 프레임워크입니다. 이 프레임워크는 타입 기반 개발을 지향하고 있으며, API 개발 시 입력값 검사과 타입 변환을 중요한 기본 기능으로 포함하고 있습니다.
하지만 TypeScript의 타입은 컴파일 타임에만 존재하고, 런타임에는 사라지기 때문에 다음과 같은 문제가 생길 수 있습니다:
  • "런턴임에 들어온 요청의 body가 지정한 DTO 타입과 일치하는가?"
  • "타입스크립트가 타입을 강제하지 않으므로, 유효성 검사가 없다면 잘못된 값도 통과합니다."
이를 해결해주는 것이 class-validatorclass-transformer입니다.

class-validator와 class-transformer란?

| 패키지 | 설명 | | --- | --- | | class-validator | 데코레이터 기반의 유효성 검사. @IsString(), @IsEmail() 등 | | class-transformer | plain object ↔ class instance 변환 (예: JSON → DTO 객체) |
NestJS에서는 이 두 패키지를 활용해 런턴임에서도 안전한 DTO 기본 검사를 할 수 있게 지원합니다.

NestJS에서는 ValidationPipe를 통해 그룹으로 연결합니다:
// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true, // class-transformer 사용
      whitelist: true, // DTO에 정의된 값만 허용
      forbidNonWhitelisted: true, // 정의되지 않은 속성은 오류
    }),
  );
  await app.listen(3000);
}


사용 예시: 유저 등록 DTO

// dto/create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator';

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

  @IsString()
  @MinLength(6)
  password: string;

  @IsString()
  nickname: string;
}

함수에서 적용:
@Post('signup')
createUser(@Body() createUserDto: CreateUserDto) {
  return this.userService.create(createUserDto);
}

class-transformer와 타입 변환

import { Type } from 'class-transformer';
import { IsDate } from 'class-validator';

export class EventDto {
  @Type(() => Date)
  @IsDate()
  date: Date;
}
해설: 만약 @Type(() => Date)가 없으면 @IsDate()는 무작위로 실패합니다. (date 는 string으로 들어오기 때문)

💡 ValidationPipe 옵션 정보

| 옵션 | 설명 | | --- | --- | | transform | JSON → class instance 변환 (타입 변환 포함) | | whitelist | DTO에 없는 속성은 제거 | | forbidNonWhitelisted | 정의되지 않은 속성이 오면 오류 | | skipMissingProperties | DTO에 없는 값은 검사하지 않음 | | enableDebugMessages | 디버그 메시지 출력 (NestJS 9+) |

Before vs After 비교

사용 전: 유효성 검사 모두 수동 구현

@Post('signup')
createUser(@Body() body: any) {
  const { email, password, nickname } = body;

  if (typeof email !== 'string' || !email.includes('@')) {
    throw new BadRequestException('유효한 이메일을 입력해주세요.');
  }

  if (typeof password !== 'string' || password.length < 6) {
    throw new BadRequestException('비밀번호는 최소 6자 이상이어야 합니다.');
  }

  return this.userService.create({ email, password, nickname });
}

사용 후: 자동 유효성 검사 + 타입 변환

@Post('signup')
createUser(@Body() createUserDto: CreateUserDto) {
  return this.userService.create(createUserDto);
}

  • DTO에 데코레이터 검사 조건을 포함
  • 자동으로 타입 확인 + 유효성 검사

🚨 자주 발생하는 실수

  • transform: true 설정 맞는가? 값 타입 변환이 안됨
  • @Type(() => Number) 바로 적용해야 타입이 변환됨
  • whitelist: true가 없으면 잘못된 필드도 통과

마무리하며

class-validatorclass-transformer는 NestJS에서 API의 신력성을 보장하는 내용으로 너무 중요한 도구입니다. 다음과 같은 다양한 이유로 사용해야 합니다.
  • 유효성 검사 자동화
  • 타입 안전성 + 자동 변환
  • Swagger, 문서, 코드 테스트에서 DTO 재사용 가능
  • 보안 강화 (불필요 필드 차단)