파이프는 Injectable()
데코레이터로 주석이 달린 클래스이며, PipeTransform 인터페이스를 구현해야 한다.
파이프는 입력 데이터를 1. 원하는 형식으로 변환(transformation) 2. 유효성 검사(validation) 으로 사용된다.
두 경우 모두 컨트롤러 라우트 핸들러가 처리하는 인수
에서 작동한다. 메소드가 호출되기 직전에 파이프를 삽입하고 파이프는 메소드로 향하는 인수를 수신하여 이에 대해 작동한다.
Nest에는 즉시 사용할 수 있는 6개의 파이프가 제공되며, 유효성 검사의 사용 사례인 ValidationPipe
에 대해 알아본다. 파이프는 @nestjs/common
패키지에서 내보내진다.
커스텀 버전을 처음부터 만들어 커스텀 파이프가 어떻게 구성되는지 살펴본다.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
모든 파이프는 PipeTransform 를 implements 하기 위해 transform()
메서드를 구현해야 한다. value 매개변수는 현재 처리된 메서드 인수, metadata는 현재 처리된 메서드 인수의 메타데이터이다.
라우트 핸들러에서 파이프가 사용되는 방법에 대해 알아본다.
// order.controller.ts
@Post()
async createOrder(@Body() orderDataDTO: OrderDataDTO) {
this.orderService.createOrder(orderDataDTO);
}
// orderDataDTO.dto.ts
export class OrderDataDTO {
barcode: number;
user: string;
}
createOrder 메서드로 들어오는 모든 요청에 유효한 본문이 포함되어 있는지 확인하고 싶다. 따라서 OrderDataDTO
객체의 두 멤버를 검증해야 한다.
1번 방법은 단일 책임 규칙(SRP, 클래스는 단 한 개의 책임을 가져야 한다)을 위반하므로 이상적이지 않다. 2번 방법은 각 메서드 시작부분에서 유효성 검사기를 호출해야 한다는 단점이 있다. 전체 애플리케이션의 모든 컨텍스트에서 사용할 수 있는 일반 미들웨어를 만드는 것이 불가능하여 3번 방법도 어렵다.
class-validation
라이브러리를 사용하여 검증 파이프를 만들어본다.
이 강력한 라이브러리를 사용하여 데코레이터 기반 유효성 검사를 할 수 있다. 데코레이터 기반 유효성 검사는 처리된 속성의 metadata에 액세스할 수 있어 Nest의 파이프 기능과 결합할 때 매우 강력하다.
$ npm i --save class-validator class-transformer
orderDataDTO 클래스에 타입 검사를 할 수 있는 데코레이터를 추가한다.
// orderDataDTO.dto.ts
import { IsString, IsInt } from 'class-validator';
export class OrderDataDTO {
@IsInt()
barcode: number;
@IsString()
user: string;
}
ValidationPipe 클래스에 라이브러리를 사용한 코드를 작성한다.
// validation.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
@Injectable()
export class ValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
transform()
메서드는 async
로 작성한다.
Nest는 동기 및 비동기 파이프를 모두 지원한다. 일부 class-validation
유효성 검사가 비동기화 될 수도 있기 때문에 메서드 자체를 async로 만든다.
헬퍼 함수 toValidate()
를 확인한다.
처리중인 현재 인수가 네이티브 자바스트립트 타입인 경우 유효성 검사 단계를 건너 뛴다.
클래스 변환기 함수 plainToClass()
를 사용하여 일반 자바스크립트 인수 객체를 타입이 지정된 객체로 변환한다.
네트워크 요청에서 역직렬화될 때 들어오는 포스트(post) 본문 객체가 아무 타입 정보도 가지고 있지 않기 때문에 장식된 형태로의 변환이 수행되어야 한다.
// order.controller.ts
@Post()
async createOrder(@Body(new ValidationPipe()) orderDataDTO: OrderDataDTO) {
this.orderService.createOrder(orderDataDTO);
}
마지막 단계는 ValidationPipe
를 바인딩하는 것이다. 파이프는 매개변수 범위, 메서드 범위, 컨트롤러 범위 또는 전역 범위일 수 있다. 위의 코드는 매개변수 범위에서 작동된 파이프 예제이다.
ValidationPipe
는 전체 애플리케이션의 모든 라우트 핸들러에 적용되도록 전역 범위(global-scoped) 파이프로 설정할 수 있다.
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
위의 방법은 전역 파이프의 바인딩이 모듈 컨텍스트 외부에서 수행되었으므로 종속성을 주입할 수 없다. 따라서 모든 모듈에서 직접 전역 파이프를 설정한다.
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
Exception Filter 가 모든 throw 에러를 잡아냈다면, Pipes는 메소드가 호출되기 전에 DTO를 검사하는 역할을 한다.
Pipes | 네스트 JS 한국어 매뉴얼
Nest.js and the Custom Validation Pipe