@Injectable()
데코레이터가 달린 클래스이며, implements PipeTransform
인터페이스
파이프는 두 가지 주요 용도로 사용
두 경우 모두 파이프는 컨트롤러 라우트 핸들러에서 처리되는 arguments
에 대해 작동
파이프는 예외 구역 내에서 실행됩니다. 즉, 파이프가 예외를 던지면 예외 레이어(전역 예외 필터 및 현재 컨텍스트에 적용된 모든 예외 필터에 의해 처리됩니다. 따라서 파이프에서 예외가 발생하면 컨트롤러 메서드는 실행되지 않습니다. 이는 외부 소스에서 애플리케이션으로 들어오는 데이터를 유효성 검사하는 최선의 방법을 제공합니다.
Nest에는 기본적으로 사용할 수 있는 아홉 가지 파이프가 있습니다:
ValidationPipe
ParseIntPipe
ParseFloatPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
ParseEnumPipe
DefaultValuePipe
ParseFilePipe
이 파이프들은 @nestjs/common
패키지에서 사용
파이프 인스턴스를 적절한 컨텍스트에 바인딩해야 사용 가능
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
다음의 호출에
GET localhost:3000/abc
다음의 예외가 발생
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
마찬가지로, 클래스를 전달하면 프레임워크가 인스턴스화 책임을 지고 의존성 주입을 활성화
@Get(':id')
async findOne(
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
id: number,
) {
return this.catsService.findOne(id);
}
ParseUUIDPipe()
를 사용할 때 버전 3, 4 또는 5의 UUID를 파싱합니다. 특정 버전의 UUID만 필요하다면 파이프 옵션에 버전을 전달할 수 있습니다.
유효성 검사 기술에서 검증 파이프의 광범위한 예제를 참조하세요.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
return value;
}
}
PipeTransform<T, R>
는 모든 파이프가 구현해야 하는 제네릭 인터페이스입니다. 제네릭 인터페이스는 입력value
의 타입을 나타내는T
와transform()
메서드의 반환 타입을 나타내는R
을 사용합니다.
모든 파이프는 PipeTransform
를 위해 transform()
메서드를 구현해야 함
value
metadata
export interface ArgumentMetadata {
type: 'body' | 'query' | 'param' | 'custom';
metatype?: Type<unknown>;
data?: string;
}
type
|
인자가 body @Body() , query @Query() , param @Param() 또는 custom 파라미터인지 여부를 나타냅니다. (자세한 내용은 여기를 참조하세요).
|
metatype
|
인자의 메타타입을 제공하며, 예를 들어 String 입니다. 주의: 라우트 핸들러 메서드 서명에서 타입 선언을 생략하거나 바닐라 JavaScript를 사용할 경우 값이 undefined 입니다.
|
data
|
데코레이터에 전달된 문자열이며, 예를 들어 @
Body('string') 입니다. 데코레이터 괄호를 비워두면undefined 입니다.
|
TypeScript 인터페이스는 트랜스파일 동안 사라집니다. 따라서 메서드 매개변수의 타입이 클래스가 아닌 인터페이스로 선언되면
metatype
값은Object
가 됩니다.
post 의 body 객체에 대한 확인 예제;
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
export class CreateCatDto {
name: string;
age: number;
breed: string;
}
create
메서드 요청이 유효한 본문을 포함하고 있는지 확인하려면, dto 객체의 세 가지 멤버를 검증
따라서, 검증 클래스를 작성 후 검증 작업을 위임
혹은, 검증 미들웨어
깔끔하고 DRY 방식으로 수행하는 여러 접근 방식 중, 스키마 기반 검증이 일반적
Zod 라이브러리는 간단한 API로 스키마를 작성할 수 있
$ npm install --save zod
schema.parse()
메서드를 적용하여 제공된 스키마에 대해 들어오는 인수를 검증앞서 언급했듯이, 검증 파이프는 값을 변경하지 않고 반환하거나 예외를 던짐
import { PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ZodSchema } from 'zod';
export class ZodValidationPipe implements PipeTransform {
constructor(private schema: ZodSchema) {}
transform(value: unknown, metadata: ArgumentMetadata) {
try {
const parsedValue = this.schema.parse(value);
return parsedValue;
} catch (error) {
throw new BadRequestException('Validation failed');
}
}
}
ZodValidationPipe
를 사용 단계:
ZodValidationPipe
인스턴스를 생성import { z } from 'zod';
export const createCatSchema = z
.object({
name: z.string(),
age: z.number(),
breed: z.string(),
})
.required();
export type CreateCatDto = z.infer<typeof createCatSchema>;
@UsePipes()
데코레이터의 구현:
@Post()
@UsePipes(new ZodValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@UsePipes()
데코레이터는@nestjs/common
패키지에서 가져옵니다.
zod
라이브러리는tsconfig.json
파일에서strictNullChecks
구성을 활성화해야 합니다.
이 섹션의 기술은 TypeScript를 필요로 하며 바닐라 JavaScript로 작성된 애플리케이션에서는 사용할 수 없습니다.
class-validator 라이브러리와 잘 작동
metatype
에 접근할 수 있기에 Pipe 기능과 결합할 때 매우 강력$ npm i --save class-validator class-transformer
CreateCatDto
클래스가 Post 본문 객체의 단일 진리의 원천으로 유지(별도의 검증 클래스를 작성할 필요가 없음)
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
class-validator 데코레이터에 대해 자세히 알아보려면 여기를 참조하세요.
ValidationPipe
클래스를 작성
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } 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 = plainToInstance(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);
}
}
직접 일반적인 검증 파이프를 작성할 필요는 없습니다. Nest는 기본적으로
ValidationPipe
를 제공합니다. 내장된ValidationPipe
는 우리가 작성한 샘플보다 더 많은 옵션을 제공하며, 자세한 내용과 많은 예제를 여기에서 확인할 수 있습니다.
위에서는 class-transformer 라이브러리를 사용했습니다. 이 라이브러리는 class-validator 라이브러리와 같은 저자가 작성했기 때문에 상호 호환성이 좋습니다.
transform()
메서드가 async
async
를 활용구조 분해 할당으로 ArgumentMetadata
에서 metatype
필드 추출
헬퍼 함수 toValidate()
plainToInstance()
함수로 타입이 지정된 객체로 변환하여 검증을 수행검증 파이프는 값을 변경하지 않고 반환하거나 예외를 던
ValidationPipe
를 바인딩
파이프 인스턴스를 라우트 핸들러 @Body()
데코레이터에 바인딩하여 파이프가 post 본문을 검증하게 함
@Post()
async create(
@Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
this.catsService.create(createCatDto);
}
전역 범위 파이프로 설정하여 애플리케이션 전체의 모든 라우트 핸들러에 적용 가능
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
하이브리드 앱의 경우
useGlobalPipes()
메서드는 게이트웨이 및 마이크로서비스에 대해 파이프를 설정하지 않습니다. "표준" (비하이브리드) 마이크로서비스 앱의 경우,useGlobalPipes()
는 파이프를 전역적으로 마운트합니다.
전역 파이프가 모듈 외부에서 등록될 경우(위 예제에서처럼 useGlobalPipes()
를 사용하여) 의존성을 주입할 수 없음
모듈 내부에서 직접 전역 파이프를 설정하여 해결 가능:
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_PIPE,
useClass: ValidationPipe,
},
],
})
export class AppModule {}
이 방법을 사용하여 파이프에 대해 의존성 주입을 수행할 때, 이 구성이 사용된 모듈에 관계없이 파이프는 실제로 전역적임을 유의하세요. 어디에서 해야 할까요? 파이프(
위 예제에서는 ValidationPipe
)가 정의된 모듈을 선택하세요. 또한,useClass
는 커스텀 제공자 등록을 처리하는 유일한 방법이 아닙니다. 자세한 내용은 여기에서 확인하세요.
직접 일반적인 검증 파이프를 작성할 필요는 없고,ValidationPipe
를 제공
내장된 ValidationPipe
는 우리가 작성한 샘플보다 더 많은 옵션을 제공하며, 자세한 내용과 많은 예제를 여기에서 확인 가능
입력 데이터를 원하는 형식으로 변환도 가능
transform
함수에서 반환된 값이 인자의 이전 값을 완전히 덮어쓰므로 가능
변환 파이프는 타입 변환 혹은 기본값 적용 등의 작업을 수행하여 클라이언트 요청과 요청 핸들러 사이에 처리 함수를 삽입 가능
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}
바인딩은:
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return this.catsService.findOne(id);
}
Parse*
파이프는 매개변수 값이 정의되어 있을 것으로 예상(기대)
null
또는 undefined
값은 예외Parse*
파이프가 이러한 값에 대해 작동하기 전에 기본 값을 제공DefaultValuePipe
를 사용하여, Parse*
파이프가 작동하기 전에 @Query()
데코레이터에서 DefaultValuePipe
인스턴스를 인스턴스:
@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 });
}