파이프
파이프는 클라이언트에서 전달된 요청 데이터를 검증 혹은 변환하는 역할을 합니다.
라우팅 핸들러(컨트롤러)가 호출되기 전에 작동합니다.
파이프 내부에서 예외 처리를 할 수 있습니다.
비동기식일 수 있습니다. (데이터베이스나 다른 서버의 검증이 필요한 경우 사용)
파이프는 보통 직접 만들지 않고, NestJS 기본 제공 파이프 혹은 class-transformer, class-validator 라이브러리를 사용합니다.
DTO(Data Transfer Object)
- 데이터의 유효성을 체크하는 타입
- 데이터를 전달하는 객체
class-transformer, class-validator를 사용한 데이터 검증 및 변환
class-transformer 라이브러리는 데이터 변환을 담당합니다.
class-validator 라이브러리는 데이터 검증을 담당합니다.
class-transformer, class-validator를 사용해 데이터 검증 및 변환을 하기 위해서는 다음 과정을 거쳐야 합니다.
- class-transformer, class-validator 패키지 추가
- main.ts 파일에 전역으로 파이프를 사용하도록 설정
- DTO(Data Transfer Object) 클래스에 데이터 검증 및 변환 규칙 정의
- 라우터에 DTO 적용
패키지 추가
npm i class-validator class-transformer
main.ts 파일에 globalPipe 적용
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000);
}
DTO 클래스에 데이터 검증 및 변환 규칙 정의
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);
}
ValidationPipe 옵션(전역 파이프 옵션)
| 옵션 | 설명 |
| whitelist | true 설정 시 DTO 속성에 정의하지 않은 request 파라미터가 있을 때 제거 |
| forbidNonWhitelisted | true 설정 시 DTO 속성에 정의하지 않은 request 파라미터가 있을 때 오류 반환 |
| transform | true 설정 시 요청 객체를 DTO에 정의한 유형으로 변환합니다. 명시적 변환 귀찮은데 이거 사실상 필수 아닌가?? |
| disableErrorMessages | true 설정 시 데이터 검증 관련 오류 메세지를 비활성화합니다. 데이터 검증 오류 메세지는 매우 자세하게 나오기 때문에 서비스 시에는 true 설정이 적절합니다. |
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가 아닌지 확인 |
| @IsOptional() | 필수값이 아님 |
| @Equals(값) | 값과 같은지 비교(타입도 비교) |
| @IsNotEquals(값) | 값과 다른지 비교(타입도 비교) |
| @IsIn(값 배열) | 배열의 값 중 하나와 일치하는지 확인 |
| @IsNotIn(값 배열) | 배열의 값 모두와 일치하지 않는지 확인 |
| @IsBoolean() | 논리형인지 확인 |
| @IsString() | 문자열인지 확인 |
| @IsDate() | Date() 타입인지 확인 |
| @IsDateString(), @IsISO8601() | 연월일 시분초 형식의 문자열인지 확인 |
| @IsNumber(옵션 객체) | 숫자형인지 확인 옵션 속성 { allowNaN : boolean, // NaN 허용여부 allowInfinity: boolean, // Infinity 허용여부 maxDecimalPlaces: number, // 최대 소수 자리수 } |
| @IsInt() | 정수인지 확인 |
| @IsPositive() | 양수인지 확인 |
| @IsNegative() | 음수인지 확인 |
| @IsFloat() | 실수인지 확인 |
| @IsArray() | 배열인지 확인 |
| @IsEnum(enum 객체) | enum 객체의 값 중 하나인지 확인 |
| @Min(값) | 최소값 설정 |
| @Max(값) | 최대값 설정 |
| @Contains(문자열) | 문자열이 포함되었는지 확인 |
| @NotContains(문자열) | 문자열이 포함되지 않았는지 확인 |
| @IsAlpha() | 문자열이 a-zA-Z인지 확인 |
| @IsAlphanumeric() | 문자열이 a-zA-Z0-9인지 확인 |
| @IsEmail(옵션 객체) | 문자열이 이메일인지 확인 옵션이 있는데 너무 많아서 생략(문서에도 안 나와있어서 열 받음) |
| @IsFQND(옵션 객체) | 문자열이 도메인 주소인지 확인 옵션이 있는데 쓸 일이 없을 듯? |
| @IsIP(버전옵션) | 문자열이 IP 주소인지 확인 버전 옵션: '4' 혹은 '6'. IP의 버전이 4인지 6인지 명시 |
| @IsJSON() | 문자열이 유효한 JSON인지 확인 |
| @IsStrongPassword(옵션객체?) | 문자열이 강력한 비밀번호로 간주될 수 있는지 여부를 확인합니다. 사용자 지정 요구 사항이나 점수 규칙을 허용합니다. returnScore 옵션이 true인 경우, 함수는 부울 값이 아닌 정수 점수를 반환합니다. 기본 옵션은 다음과 같습니다. point로 시작하는 것들은 returnScore에서 계산하는 값인듯? { minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 } |
| @Length(최소 길이, 최대 길이) | 문자열의 최소 길이, 최대 길이 지정 최소 길이는 필수, 최대 길이는 옵션 |
| @MinLength(최소 길이) | 문자열의 최소 길이 지정 |
| @MaxLength(최대 길이) | 문자열의 최대 길이 지정 |
| @Matches(정규식패턴, 수정자?) @Matches(정규식패턴, 옵션객체) |
정규식과 일치하는지 확인. 수정자가 왜 있는지도 모르겠고, 도대체 어떻게 사용하는지도 모르겠습니다. 옵션 객체에는 message를 전달해 오류 메세지를 전달할 수 있습니다. { message: `오류입니다`} |
| @ArrayNotEmpty() | 비어있지 않은 배열인지 확인 |
| @ArrayUnique() | 배열의 모든 값이 유니크 값인지 확인 |
옵션
class-validator의 데코레이터 사용 시 객체를 전달해 옵션을 사용할 수 있습니다.
@IsInt({ message: '정수형만 사용 가능합니다' })
id: number;
@MaxLength(20, {
each: true,
})
tags: string[];
| 옵션 | 설명 |
| message | 오류 발생 시 전달할 메세지 |
| each | 배열, Set, Map의 경우 배열 요소 각각에 옵션을 검사할지 여부 |
오류 발생 시 반환하는 객체
| target | 검증한 객체 |
| property | 검증 실패한 프로퍼티 |
| value | 검증 실패한 값 |
| constraints | 에러를 발생시킨 조건 |
| children | 검증 실패한 프로퍼티의 모든 제약 조건 |
중첩 DTO
하위 DTO
export class Metadata {
@IsString()
@IsNotEmpty()
key: string;
@IsNotEmpty()
value: any;
}
상위 DTO
export class Data {
// 배열이 아닌 경우
@IsOptional()
@ValidateNested()
@Type(() => Metadata)
metadata?: Metadata;
// 배열인 경우
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
@Type(() => Metadata)
arrayMetadata?: Metadata[];
}
배열 유효성 검사
@Post()
createBulk(
@Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
class-validator를 이용한 사용자 정의 검증기
정의
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
@ValidatorConstraint({ name: 'customText', async: false })
export class CustomTextLength implements ValidatorConstraintInterface {
validate(text: string, args: ValidationArguments) {
return text.length > 1 && text.length < 10;
}
defaultMessage(args: ValidationArguments) {
// 오류 발생 시 반환할 메세지
return '($value)은 너무 짧거나 깁니다!';
}
}
사용
import { Validate } from 'class-validator';
import { CustomTextLength } from './CustomTextLength';
export class Post {
@Validate(CustomTextLength)
title: string;
}
사용자 정의 데코레이터를 이용한 class-validator를 이용한 사용자 정의 검증기 사용
정의
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
import { CustomTextLength } from './CustomTextLength';
export function CustomTextLengthDecorator(property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor, // 검증할 객체
propertyName: propertyName, // 검증할 프로퍼티명
options: validationOptions, // 데코레이터 사용 시 전달되는 option을 사용하기 위해 설정
validator: CustomTextLength, // 사용자 정의 validator를 validator 프로퍼티에 전달
},
});
};
}
사용
import { CustomTextLengthDecorator } from './CustomTextLengthDecorator';
export class User {
@CustomTextLengthDecorator()
name: string;
}
class-transformer
class-transformer는 데이터를 변환할 때 사용하는 라이브러리입니다.
데코레이터
| 데코레이터 | 설명 |
| @Type( () => 자료형 ) | 원래 자료형을 익명함수에서 반환한 자료형으로 변경. 타입스크립트가 아니라 자바스크립트의 자료형을 사용해야 합니다. 보통 첫 글자가 소문자면 타입스크립트의 자료형이고, 첫 글자가 대문자면 자바스크립트의 자료형입니다. |
| @Exclude( { 옵션객체 } ) | 하나의 DTO를 request, response에서 사용할 때 요청 객체와 응답 객체에 필요한 데이터가 다를 때 데이터를 제외하기 위해 사용합니다. 프로퍼티 대신 클래스에 선언할 수 있습니다. 두 가지 옵션이 있습니다. toPlainOnly: 클래스를 객체로 변환 시. reponse에 사용 toClassOnly: 객체를 클래스로 변환 시. request에 사용 |
| @Expose( {옵션객체} ) | 프로퍼티의 getter를 만들어줍니다. 프로퍼티에도 사용할 수 있습니다. (클래스를 @Exclude()로 변경 후 필요한 것만 포함) 옵션객체에 name: string을 전달해 프로퍼티명이 아니라 다른 이름으로 객체를 전달 할 수 있습니다. |
@Transform() 데코레이터를 사용한 데이터 변환
클라이언트가 전달한 데이터를 콜백 함수의 반환값으로 수정합니다.
콜백 함수의 파라미터 선언부에 객체를 전달한 이유는 콜백 함수에 실제로 객체가 전달되기 때문입니다.
| 속성 | 설명 |
| 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;
}
문자열을 숫자, 날짜 형식으로 변환
import { Type } from 'class-transformer'
export class UpdatePostDto {
@Type(()=> Number)
id: number;
@Type(()=> Date)
updateDate: date;
}
Mapped Types
패키지 설치
npm install @nestjs/mapped-types
제공 타입
| 타입 | 설명 |
| PartialType | 기존 DTO의 모든 속성들을 선택적으로 사용 가능한 DTO 생성 |
| PickType | 기존 DTO의 속성 중 사용 가능한 일부를 명시한 DTO 생성 |
| OmitType | 기존 DTO의 속성 중 사용 불가능한 일부를 명시한 DTO 생성 |
| IntersectionType | 두 DTO의 모든 속성을 결합한 DTO 생성 |
예제
create-cat.dto.ts
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
OmitType
export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}
두 개 이상의 Mapped Type 조합
export class UpdateCatDto extends PartialType(
OmitType(CreateCatDto, ['name'] as const),
) {}
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(옵션): 처리할 파라미터의 메타데이터가 포함된 객체
- type: 파라미터의 종류. body, query, param, custom 중 하나
- metatype: 파라미터의 자료형. 핸들러에서 타입을 생략하면 undefined가 됩니다.
- data: 매개변수의 이름
transform() 메서드에서 반환된 결과는 라우팅 핸들러에 전달됩니다.
예외 발생 시 클라이언트에 바로 결과를 반환합니다.
구현 예시
import { Injectable, ArgumentMetadata, PipeTransform } from '@nestjs/common';
@Injectable()
export class CustomPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata): any {
return value;
}
}'NestJS > NestJS 자체 문서화' 카테고리의 다른 글
| 가드(Guard) (0) | 2025.12.31 |
|---|---|
| 예외 필터 (0) | 2025.12.31 |
| 프로바이더, 서비스 (0) | 2025.12.31 |
| 컨트롤러 (0) | 2025.12.31 |
| 모듈 (0) | 2025.12.31 |