개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

NestJS DTO — 계층 간 데이터를 전달하는 객체

2025년 3월 3일
NestJSDTOclass-validatorCreateUserDtoValidation

API 요청이 Controller에 도달했을 때, 그 데이터가 올바른 형태인지 어떻게 보장할 수 있을까. NestJS는 DTO(Data Transfer Object) 패턴과 class-validator를 결합해 입력 검증과 타입 안전성을 동시에 확보한다. DTO는 단순한 타입 정의가 아니라, 계층 간 데이터 계약(contract)을 명시하는 역할을 한다.

DTO란 무엇인가

DTO는 한 계층에서 다른 계층으로 데이터를 전달하기 위해 정의하는 객체다. 비즈니스 로직을 갖지 않으며, 오직 데이터의 형태만 기술한다.

HTTP Request
    │
    ▼
┌─────────────┐
│  Controller │  ← DTO로 요청 바디를 받음
└──────┬──────┘
       │ DTO
       ▼
┌─────────────┐
│   Service   │  ← DTO를 받아 도메인 로직 실행
└──────┬──────┘
       │ Entity / ResponseDto
       ▼
┌─────────────┐
│ Repository  │  ← DB와 직접 통신
└─────────────┘

CreateUserDto / UpdateUserDto / ResponseDto

세 가지 DTO는 각각 다른 책임을 가진다.

DTO용도특징
CreateUserDto생성 요청 바디필수 필드 전체 포함
UpdateUserDto수정 요청 바디모든 필드가 선택적
ResponseDto응답 직렬화민감 필드 제외
// create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';

export class CreateUserDto {
  @IsString()
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(8)
  password: string;
}

UpdateUserDto는 PartialType을 활용해 중복 없이 선택적 필드를 만든다.

// update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

PartialType: @nestjs/mapped-types 패키지가 제공하는 유틸리티로, 부모 DTO의 모든 필드를 optional로 변환한다.

// response-user.dto.ts
import { Exclude, Expose } from 'class-transformer';

@Exclude()
export class ResponseUserDto {
  @Expose()
  id: number;

  @Expose()
  name: string;

  @Expose()
  email: string;

  // password는 @Expose() 없으므로 직렬화에서 제외됨
}

class-validator 데코레이터

class-validator는 데코레이터 기반으로 필드 유효성 규칙을 선언한다. 자주 쓰는 데코레이터는 아래와 같다.

데코레이터검증 내용
@IsString문자열 여부
@IsEmail이메일 형식
@IsInt정수 여부
@MinLength(n)최소 문자 길이
@IsOptionalundefined 허용
@IsEnum(E)열거형 값 여부

ValidationPipe와 연동

DTO의 데코레이터는 ValidationPipe가 실행해야 효과가 있다. main.ts에서 전역으로 등록하는 것이 일반적이다.

// main.ts
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, // 불필요한 필드 포함 시 400 반환
      transform: true,       // 요청 바디를 DTO 인스턴스로 자동 변환
    }),
  );
  await app.listen(3000);
}
bootstrap();

whitelist: true 옵션은 DTO에 정의되지 않은 키를 요청에서 자동으로 걷어낸다. 클라이언트가 의도치 않은 필드를 보내더라도 서비스 계층에 전달되지 않는다.

Controller에서는 @Body 데코레이터에 DTO 타입을 지정하면 된다.

@Post()
create(@Body() createUserDto: CreateUserDto) {
  return this.usersService.create(createUserDto);
}

transform: true를 활성화하면 @Body가 반환하는 값이 plain object가 아닌 CreateUserDto 클래스 인스턴스가 되어, 타입 안전성이 런타임까지 보장된다.

타입 안전성의 실질적 의미

TypeScript의 타입은 컴파일 타임에만 존재한다. 런타임에 외부 데이터가 들어오는 순간 타입 정보는 사라진다. class-validator + ValidationPipe의 조합은 이 간극을 메운다. DTO 클래스가 런타임 검증의 기준이 되어, 타입 선언과 실제 동작이 일치하게 된다.

← 이전 글NestJS Decorator — 클래스와 메서드에 메타데이터를 추가하는 문법
다음 글 →NestJS Entity — 데이터베이스 테이블과 매핑되는 클래스