파이프의 역할과 특징
파이프는 클라이언트에서 전달된 요청 데이터를 검증 혹은 변환하는 역할을 합니다.
파이프의 특징
라우팅 핸들러(컨트롤러)가 호출되기 전에 작동합니다.
파이프 내부에서 예외 처리를 할 수 있습니다.
비동기식일 수 있습니다.
@Injectoble() 데코레이터가 있는 클래스입니다.
파이프는 보통 직접 만들지 않고, NestJS 기본 제공 파이프 혹은 class-transformer, class-validator 패키지를 사용합니다.
class-transformer, class-validator를 사용한 데이터 검증 및 변환
class-transformer, class-validator를 사용해 데이터 검증 및 변환을 하기 위해서는 다음 과정을 거쳐야 합니다.
- main.ts 파일에 전역으로 파이프를 사용하도록 설정(옵션 객체를 전달하지 않으면 필요 없는 과정입니다)
- DTO(Data Transfer Object) 클래스에 데이터 검증 및 변환 규칙 정의
- 라우터에 DTO 적용
main.ts 파일에 globalPipe 적용
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000);
}
DTO 클래스에 데이터 검증 및 변환 규칙 정의
DTO에 규칙을 적용하기 전에 class-validator, class-transformer 패키지를 추가해야 합니다.
npm install class-validator class-transformer
DTO 파일에 class-validator의 데코레이터 적용
import { IsNotEmpty } from 'class-validator';
export class CreateTaskDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
description: string;
}
라우터에 DTO 적용
@Post()
async createTask(@Body() createTaskDto: CreateTaskDto) {
return await this.taskService.createTask(createTaskDto);
}
class-validator와 class-transformer가 작동하는 방식(파이프를 전역에 적용해도 괜찮은 이유)
데이터 검증 시 class-validator와 class-transformer가 생성해주는 메타데이터를 이용해 데이터를 검증하기 때문에 파이프를 전역에서 사용하도록 설정해도 문제가 발생하지 않습니다.
DTO에 class-validator와 class-transformer 데코레이터를 적용하지 않으면 런타임에 실행되는 자바스크립트 클래스 파일에는 아무런 메타데이터도 갖고 있지 않습니다.
create-task.dto.ts 파일
export class CreateTaskDto {
title: string;
description: string;
}
/dist 폴더에 생성되는 create-task.dto.js 파일
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateTaskDto = void 0;
class CreateTaskDto {
}
exports.CreateTaskDto = CreateTaskDto;
//# sourceMappingURL=create-task.dto.js.map
DTO 파일에 데코레이터를 적용하면 자바스크립트 클래스 파일에 메타데이터가 추가됩니다.
create-task.dto.ts 파일
import { Transform } from 'class-transformer';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateTaskDto {
@Transform(({ value }) => String(value))
@IsNotEmpty()
@IsString()
title: string;
@Transform(({ value }) => String(value))
@IsNotEmpty()
@IsString()
description: string;
}
/dist 폴더에 생성되는 create-task.dto.js 파일
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateTaskDto = void 0;
const class_transformer_1 = require("class-transformer");
const class_validator_1 = require("class-validator");
class CreateTaskDto {
}
exports.CreateTaskDto = CreateTaskDto;
__decorate([
(0, class_transformer_1.Transform)(({ value }) => String(value)),
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.IsString)(),
__metadata("design:type", String)
], CreateTaskDto.prototype, "title", void 0);
__decorate([
(0, class_transformer_1.Transform)(({ value }) => String(value)),
(0, class_validator_1.IsNotEmpty)(),
(0, class_validator_1.IsString)(),
__metadata("design:type", String)
], CreateTaskDto.prototype, "description", void 0);
//# sourceMappingURL=create-task.dto.js.map
class-validator
class-validator는 데이터의 유효성을 검증할 때 사용하는 라이브러리입니다.
자주 사용할 것 같은 데코레이터만 정리했습니다.
데코레이터 | 설명 |
@IsNotEmpty() | null, '', undefined가 아닌지 확인 |
@IsIn(값 배열) | 배열의 값 중 하나와 일치하는지 확인 |
@IsNotIn(값 배열) | 배열의 값 모두와 일치하지 않는지 확인 |
@IsBoolean() | 논리형인지 확인 |
@IsDate() | Date() 타입인지 확인 |
@IsISO8601() | 연월일 시분초 형식의 Date() 타입인지 확인 |
@IsNumber(옵션 객체) | 숫자형인지 확인 옵션 속성 { allowNaN : boolean, // NaN 허용여부 allowInfinity: boolean, // Infinity 허용여부 maxDecimalPlaces: number, // 최대 소수 자리수 } |
@IsInt() | 정수인지 확인 |
@IsFloat() | 실수인지 확인 |
@IsArray() | 배열인지 확인 |
@Min() | 최소값 설정 |
@Max() | 최대값 설정 |
@Contains(문자열) | 문자열이 포함되었는지 확인 |
@NotContains(문자열) | 문자열이 포함되지 않았는지 확인 |
@IsAlpha() | 문자열이 a-zA-Z인지 확인 |
@IsAlphanumeric() | 문자열이 a-zA-Z0-9인지 확인 |
@IsEmail(옵션 객체) | 문자열이 이메일인지 확인 옵션이 있는데 너무 많아서 생략(문서에도 안 나와있어서 열 받음) |
@IsFQND(옵션 객체) | 문자열이 도메인 주소인지 확인 옵션이 있는데 쓸 일이 없을 듯? |
@IsIP(버전옵션) | 문자열이 IP 주소인지 확인 버전 옵션: '4' 혹은 '6'. IP의 버전이 4인지 6인지 명시 |
@IsJSON() | 문자열이 유효한 JSON인지 확인 |
@Length(최소 길이, 최대 길이) | 문자열의 최소 길이, 최대 길이 지정 최소 길이는 필수, 최대 길이는 옵션 |
@MinLength(최소 길이) | 문자열의 최소 길이 지정 |
@MaxLength(최대 길이) | 문자열의 최대 길이 지정 |
@Matches(정규식패턴, 수정자?) @Matches(정규식패턴, 옵션객체) |
정규식과 일치하는지 확인. 수정자가 왜 있는지도 모르겠고, 도대체 어떻게 사용하는지도 모르겠습니다. 옵션 객체에는 message를 전달해 오류 메세지를 전달할 수 있습니다. { message: `오류입니다`} |
@ArrayNotEmpty() | 비어있지 않은 배열인지 확인 |
@ArrayUnique() | 배열의 모든 값이 유니크 값인지 확인 |
@IsArray() @IsString({ each: true }) @MinLength(3, { each: true }) |
배열 각각의 요소가 string인지 확인 배열 각각의 요소의 길이가 3이상인지 확인 |
옵션
class-validator의 데코레이터 사용 시 객체를 전달해 옵션을 사용할 수 있습니다.
@IsInt({ message: '정수형만 사용 가능합니다' })
id: number;
@MaxLength(20, {
each: true,
})
tags: string[];
옵션 | 설명 |
message | 오류 발생 시 전달할 메세지 |
each | 배열, Set, Map의 경우 배열 요소 각각에 옵션을 검사할지 여부 |
class-transformer
class-transformer는 데이터를 변환할 때 사용하는 라이브러리입니다.
@Transform() 데코레이터
데코레이터에 콜백 함수를 전달해 사용합니다.
입력된 데이터를 콜백 함수의 반환값으로 수정합니다.
콜백 함수의 파라미터 선언부에 객체를 전달한 이유는 콜백 함수에 실제로 객체가 전달되기 때문입니다.
value 말고는 사용할 것 같지는 않지만 정리해둡니다.
속성 | 설명 |
value | 값 |
key | 키 |
obj | 값이 담긴 객체 전체 |
type | 변형 유형 |
options | 콜백 함수에서 사용할 수 있는 옵션 객체 |
import { Transform } from 'class-transformer';
export class Post {
id: number;
@Transform(({ value }) => trim(value) )
title: string;
@Transform(({ value }) => trim(value) )
content: string;
}
쓸지도 모를 데코레이터들
@Transform() 데코레이터 말고는 쓸 것 같지 않지만 혹시 몰라 정리합니다.
데코레이터 | 설명 |
@Type( () => 자료형 ) | 원래 자료형을 익명함수에서 반환한 자료형으로 변경. 타입스크립트가 아니라 자바스크립트의 자료형을 사용해야 합니다. 보통 첫 글자가 소문자면 타입스크립트의 자료형이고, 첫 글자가 대문자면 자바스크립트의 자료형입니다. |
@Exclude( { 옵션객체 } ) | 하나의 DTO를 request, response에서 사용할 때 요청 객체와 응답 객체에 필요한 데이터가 다를 때 데이터를 제외하기 위해 사용합니다. 두 가지 옵션이 있습니다. toPlainOnly: 클래스를 객체로 변환 시. reponse에 사용 toClassOnly: 객체를 클래스로 변환 시. request에 사용 |
@Expose( {옵션객체} ) | 클래스의 프로퍼티가 아니라 getter나 메서드가 반환하는 값을 전달합니다. 프로퍼티에도 사용할 수 있습니다. 옵션객체에 name: string을 전달해 프로퍼티명이 아니라 다른 이름으로 객체를 전달 할 수 있습니다. |
특정 프로퍼티만 전달하기
class에 @Exclude() 데코레이터를 사용하고, 전달하고 싶은 프로퍼티에만 @Expose() 데코레이터를 사용하면 @Expose() 데코레이터를 사용한 프로퍼티만 전달할 수 있습니다.
아래 예시는 password 프로퍼티를 전달하지 않습니다.
import { Exclude, Expose } from 'class-transformer';
@Exclude()
export class User {
@Expose()
id: number;
@Expose()
email: string;
password: string;
}
ValidationPipe 옵션
ValidationPipe() 사용 시 옵션 객체를 전달할 수 있습니다.
ValidationPipe()를 사용하지 않아도class-validation, class-transformer 라이브러리가 작동하기 때문에 옵션 객체를 사용하지 않으면 코드에 추가할 필요는 없습니다.
옵션 객체의 속성은 문서에서 확인할 수 있습니다.
옵션 | 설명 |
whitelist | 기본값 false. true 설정 시 DTO 속성에 class-validator 데코레이터가 존재하지 않으면 request 객체에서 제거. |
forbidNonWhitelisted | 기본값 false. true 설정 시 DTO 속성에 class-validator 데코레이터가 존재하지 않는 속성이 request 객체에 있을 때 오류 반환 Get 이외의 메서드에서는 사용할 지도 모르겠네요. |
NestJS 기본 제공 파이프
@nestjs/common 모듈에 9개의 기본 파이프가 제공됩니다.
class-transformer, class-validator 대신 기본 제공 파이프를 사용할 일은 거의 없습니다.
기본 제공 파이프를 사용하는 것은 단일 책임 원칙에 위배되기 때문에 사용하지 않는 것을 권장합니다.
request 객체는 모두 string으로 전달됩니다.
Parse[자료형]Pipe들은 string으로 전달되는 request 객체를 해당 자료형으로 변경합니다. 변경이 불가능하면 클라이언트에 오류를 반환합니다.
파이프 | 설명 |
ValidationPipe | (DTO 혹은 전송된 데이터)와 가장 비슷한 클래스의 전체 속성에 대한 호환성을 검증합니다. 속성 매핑이 제대로 이루어지지 않는 경우 유효성 검사가 실패합니다. |
DefaultValuePipe | 파라미터에 null 혹은 undefined가 전달되면 기본값으로 변경 |
ParseIntPipe | 파라미터를 정수형으로 변경. 변경 불가능 시 에러 처리 |
ParseFloatPipe | 파라미터를 실수형으로 변경. 변경 불가능 시 에러 처리 |
ParseBoolPipe | 파라미터를 논리형으로 변경. 변경 불가능 시 에러 처리 |
ParseArrayPipe | 파라미터를 배열로 변경. 변경 불가능 시 에러 처리 |
ParseUUIDPipe | 파라미터를 UUID로 변경. 변경 불가능 시 에러 처리 |
ParseEnumPipe | 파라미터를 Enum으로 변경. 변경 불가능 시 에러 처리 |
ParseFilePipe | 파라미터를 파일로 변경. 변경 불가능 시 에러 처리 |
ParseDatePipe | 파라미터를 Date 객체로 변경. 변경 불가능 시 에러 처리 |
기본 제공 파이프 적용 방법
기본 제공 파이프는 매개 변수에 적용합니다.
파이프는 의존성 주입 대상이기 때문에 새로운 인스턴스를 만들지 않고 사용할 수 있습니다.
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
파이프에 오류 코드나 오류 메세지 같은 옵션을 전달하고 싶으면 새로운 인스턴스를 만들어야 합니다.
@Get(':id')
async findOne(
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
id: number,
) {
return this.catsService.findOne(id);
}
하나의 매개 변수에 여러 개의 파이프를 사용할 수 있습니다.
@Get()
async findAll(
@Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
@Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
return this.catsService.findAll({ activeOnly, page });
}
사용자 정의 파이프
사용자 정의 파이프 구현 시 PipeTransform 인터페이스를 구현해야 합니다.
PipeTransform 인터페이스 구현 시 transform(value: any, metadata: ArgumentMetadata) 메서드를 구현해야 합니다.
transform 메서드
transform() 메서드는 두 개의 파라미터를 가집니다.
- value: 처리 할 파라미터의 값
- metadata(옵션): 처리할 파라미터의 메타데이터가 포함된 객체
transform() 메서드에서 반환된 결과는 라우팅 핸들러에 전달됩니다.
예외 발생 시 클라이언트에 바로 결과를 반환합니다.
구현 예시
import { Injectable, ArgumentMetadata, PipeTransform } from '@nestjs/common';
@Injectable()
export class CustomPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata): any {
return value;
}
}
'NestJS > NestJS 문서화' 카테고리의 다른 글
인터셉터(Interceptor) (0) | 2025.05.09 |
---|---|
가드(Guard) (0) | 2025.05.09 |
모델과 DTO (0) | 2025.05.02 |
예외 필터 (0) | 2025.04.24 |
프로바이더와 서비스 (0) | 2025.04.23 |