NestJS에서 클라이언트가 보내는 데이터는 항상 신뢰할 수 없다. Pipe는 Controller에 도달하기 전에 데이터를 검증(Validation)하거나 원하는 타입으로 변환(Transformation)하는 역할을 담당한다. 잘못된 입력을 Controller 안쪽까지 끌고 들어가는 대신, 경계에서 즉시 차단하거나 정제하는 것이 핵심이다.
Client Request
│
▼
[ Middleware ]
│
▼
[ Guard ]
│
▼
[ Interceptor (before) ]
│
▼
[ Pipe ] ◀── 여기서 검증 / 변환
│
▼
[ Controller Handler ]
Pipe는 Guard 이후, Controller Handler 직전에 실행된다. 검증 실패 시 Handler는 아예 호출되지 않고 예외가 즉시 반환된다.
모든 Pipe는 PipeTransform 인터페이스를 구현해야 한다. transform(value, metadata) 메서드 하나로 구성된다.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class UpperCasePipe implements PipeTransform {
transform(value: string, metadata: ArgumentMetadata): string {
return value.toUpperCase();
}
}
ArgumentMetadata — type('body' | 'query' | 'param' | 'custom'), metatype, data(데코레이터에 전달된 키 이름)를 포함한다.
transform이 값을 반환하면 그 값이 Handler에 전달되고, 예외를 던지면 즉시 에러 응답이 반환된다.
NestJS는 자주 쓰이는 변환 Pipe를 기본 제공한다.
| Pipe | 역할 |
|---|---|
ParseIntPipe | 문자열을 정수로 변환, 실패 시 400 반환 |
ParseUUIDPipe | UUID 형식 검증 |
ParseBoolPipe | 'true'/'false' 문자열을 boolean으로 변환 |
DefaultValuePipe | 값이 undefined일 때 기본값 설정 |
ValidationPipe | class-validator 데코레이터 기반 전체 검증 |
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
'abc'처럼 정수로 변환 불가능한 값이 오면 ParseIntPipe가 자동으로 400 Bad Request를 반환한다.
ValidationPipe는 class-validator와 class-transformer 라이브러리를 활용해 DTO 클래스에 선언된 데코레이터를 기반으로 검증을 수행한다.
npm install class-validator class-transformer
// create-user.dto.ts
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
// users.controller.ts
@Post()
create(@Body(ValidationPipe) dto: CreateUserDto) {
return this.usersService.create(dto);
}
class-validator — 데코레이터 방식으로 클래스 프로퍼티에 검증 규칙을 선언하는 라이브러리. class-transformer와 함께 사용해야 plain object를 클래스 인스턴스로 변환할 수 있다.
Pipe는 메서드 단위, 컨트롤러 단위, 전역 단위로 적용할 수 있다.
// 전역 적용 — main.ts
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
// 컨트롤러 또는 메서드 단위
@UsePipes(new ValidationPipe())
@Post()
create(@Body() dto: CreateUserDto) { ... }
whitelist: true는 DTO에 정의되지 않은 프로퍼티를 자동으로 제거한다. forbidNonWhitelisted: true를 함께 설정하면 허용되지 않은 필드가 포함될 경우 400 에러를 반환해 보안성을 높인다.
변환에 실패했을 때 직접 예외를 제어하려면 BadRequestException을 던진다.
@Injectable()
export class ParsePositiveIntPipe implements PipeTransform {
transform(value: string): number {
const val = parseInt(value, 10);
if (isNaN(val) || val <= 0) {
throw new BadRequestException(`${value}는 양의 정수가 아닙니다.`);
}
return val;
}
}
Controller 안에서 if 분기로 같은 처리를 반복하는 대신, Pipe 하나로 재사용 가능한 검증 단위를 만들 수 있다.