Pipes

Pipes

파이프는 @Injectable() 데코레이터로 주석이 달린 클래스입니다. 파이프는 PipeTransform 인터페이스를 구현해야 합니다.

파이프에는 두 가지 일반적인 사용 사례가 있습니다.

  • transformation: 입력 데이터를 원하는 출력으로 변환

  • validation: 입력 데이터를 평가하고 유효하다면 변경없이 그대로 전달하십시오. 그렇지 않으면 데이터가 정확하지 않을 때 예외를 던집니다

두 경우 모두 파이프는 컨트롤러 경로 처리기에 의해 처리되는 인수에서 작동합니다. Nest는 메소드가 호출되기 직전에 파이프를 삽입하고 파이프는 메소드로 향하는 인수를 수신합니다. 이 시점에서 모든 변환 또는 유효성 검사 작업이 수행 된 후 (잠재적으로) 변환 된 인수를 사용하여 경로 처리기가 호출됩니다.

info 힌트 파이프는 예외 영역 내에서 작동합니다. 이것은 파이프가 예외를 던질 때 예외 계층 (전역 예외 필터 및 현재 컨텍스트에 적용되는 예외 필터에 의해 처리됨을 의미합니다. 위와 같이 파이프에서 예외가 발생하면 이후에 컨트롤러 메소드가 실행되지 않습니다.

Built-in pipes

Nest에는 즉시 사용 가능한 3 가지 파이프 (ValidationPipe), ParseIntPipe 및 ParseUUIDPipe가 있습니다. 그것들은 @nestjs/common 패키지에서 내보내집니다. 그들이 어떻게 작동하는지 더 잘 이해하기 위해 처음부터 다시 만들어 봅시다.

ValidationPipe부터 시작하겠습니다. 처음에는 단순히 입력 값을 가져 와서 동일 기능으로 동작하는 같은 값을 즉시 반환합니다.

@@filename(validation.pipe)
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
@@switch
import { Injectable } from '@nestjs/common';

@Injectable()
export class ValidationPipe {
  transform(value, metadata) {
    return value;
  }
}

info 힌트 PipeTransform <T, R>T가 입력 value의 유형을 나타내고 Rtransform()메소드의 리턴 유형을 나타내는 일반 인터페이스입니다.

모든 파이프는 transform()메소드를 제공해야 합니다. 이 방법에는 두 가지 매개 변수가 있습니다.

  • value

  • metadata

value는 현재 처리 된 인수 (라우트 처리 방법에 의해 수신되기 전에)이며 metadata는 메타 데이터입니다. 메타 데이터 개체에는 다음과 같은 속성이 있습니다.

export interface ArgumentMetadata {
  readonly type: 'body' | 'query' | 'param' | 'custom';
  readonly metatype?: Type<any>;
  readonly data?: string;
}

이러한 속성은 현재 처리 된 인수를 설명합니다.

type

인수가 body @Body(), query @Query(), param @Param() 또는 사용자 정의 매개 변수인지 여부를 나타냅니다 (여기에서 자세히 알아보십시오).

metatype

인수의 메타 타입 (예: String)을 제공합니다. 참고: 라우트 핸들러 메소드 서명에서 유형 선언을 생략하거나 바닐라 JavaScript를 사용하는 경우 값은 undefined입니다.

data

문자열이 데코레이터에 전달되었습니다 (예: @Body('string')). 데코레이터 괄호를 비워두면 정의되지 않습니다(undefined).

warning 경고 변환 중에 TypeScript 인터페이스가 사라집니다. 따라서 메소드 매개 변수의 유형이 클래스 대신 인터페이스로 선언되면 metatype 값은 Object가됩니다.

Validation use case

CatsControllercreate()메소드를 자세히 살펴 보자.

@@filename()
@Post()
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
async create(@Body() createCatDto) {
  this.catsService.create(createCatDto);
}

createCatDto 본문 매개 변수에 초점을 맞추겠습니다. 유형은 CreateCatDto입니다.

@@filename(create-cat.dto)
export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

create 메소드로 들어오는 모든 요청에 유효한 본문이 포함되도록 합니다. 따라서 createCatDto 객체의 세 멤버를 확인해야합니다. 라우트 핸들러 메소드 내에서 이를 수행 할 수 있지만 SRP (단일 책임 규칙)를 위반합니다. 또 다른 방법은 유효성 검사 클래스를 생성하고 거기에서 작업을 위임하는 것이지만 각 방법의 시작 부분에서 이 유효성 검사기를 사용해야합니다. 유효성 검증 미들웨어를 작성하는 것은 어떻습니까? 이 방법은 좋은 아이디어일 수 있지만 전체 애플리케이션에서 사용할 수있는 일반 미들웨어를 생성 할 수는 없습니다 (미들웨어는 호출될 핸들러 및 그 매개 변수 중 하나).

이것은 파이프에 이상적인 경우 인 것으로 판명되었습니다. 계속해서 하나 만들어 봅시다.

Object schema validation

개체 유효성 검사에 사용할 수있는 몇 가지 방법이 있습니다. 한 가지 일반적인 접근 방식은 스키마 기반 유효성 검사를 사용하는 것입니다. Joi 라이브러리를 사용하면 읽을 수 있는 API를 사용하여 매우 간단한 방식으로 스키마를 만들 수 있습니다. Joi 기반 스키마를 사용하는 파이프를 살펴 보겠습니다.

필요한 패키지를 설치하여 시작하십시오.

$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi

아래 코드 샘플에서 스키마를 생성자인수로 취하는 간단한 클래스를 만듭니다. 그런 다음 Joi.validate()메소드를 적용하면 제공된 스키마에 대해 들어오는 인수의 유효성을 검사합니다.

앞에서 언급했듯이 유효 파이프(Validation Pipe)는 값을 변경하지 않고 반환하거나 예외를 발생시킵니다.

다음 섹션에서는 @UsePipes()데코레이터를 사용하여 주어진 컨트롤러 메소드에 적절한 스키마를 제공하는 방법을 살펴 봅니다.

@@filename()
import * as Joi from '@hapi/joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
  constructor(private readonly schema: Object) {}

  transform(value: any, metadata: ArgumentMetadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}
@@switch
import * as Joi from '@hapi/joi';
import { Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class JoiValidationPipe {
  constructor(schema) {
    this.schema = schema;
  }

  transform(value, metadata) {
    const { error } = Joi.validate(value, this.schema);
    if (error) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

Binding pipes

파이프를 바인딩 (적절한 컨트롤러 나 핸들러에 연결)하는 것은 매우 간단합니다. 우리는 @UsePipes()데코레이터를 사용하고 파이프 인스턴스를 생성하여 Joi 유효성 검사 스키마를 전달합니다.

@@filename()
@Post()
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
@@switch
@Post()
@Bind(Body())
@UsePipes(new JoiValidationPipe(createCatSchema))
async create(createCatDto) {
  this.catsService.create(createCatDto);
}

Class validator

warning 경고 이 섹션의 기술에는 TypeScript가 필요하며 앱이 vanilla JavaScript를 사용하여 작성된 경우에는 사용할 수 없습니다.

검증 기술의 대체 구현을 살펴 봅니다.

Nest는 class-validator 라이브러리와 잘 작동합니다. 이 놀라운 라이브러리를 사용하면 데코레이터 기반 검증을 사용할 수 있습니다. 데코레이터 기반 검증은 처리된 속성의 메타 타입(metatype)에 액세스할 수 있기 때문에 특히 Nest의 파이프 기능과 결합될 때 매우 강력합니다. 시작하기 전에 필요한 패키지를 설치해야합니다.

$ npm i --save class-validator class-transformer

이것들이 설치되면, CreateCatDto 클래스에 데코레이터 몇 개를 추가 할 수 있습니다.

@@filename(create-cat.dto)
import { IsString, IsInt } from 'class-validator';

export class CreateCatDto {
  @IsString()
  readonly name: string;

  @IsInt()
  readonly age: number;

  @IsString()
  readonly breed: string;
}

Info 힌트 클래스 유효성 검사기 데코레이터에 대한 자세한 내용은 여기를 참조하십시오.

이제 ValidationPipe 클래스를 만들 수 있습니다.

@@filename(validation.pipe)
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);
  }
}

warning 알림 위에서, 우리는 class-transformer 라이브러리를 사용했습니다. class-validator 라이브러리와 동일한 저자에 의해 만들어 졌으므로 결과적으로 매우 잘 작동합니다.

이 코드를 살펴 보겠습니다. 먼저, transform()함수는 async입니다. Nest는 동기식 파이프와 비동기식 파이프를 모두 지원하기 때문에 가능합니다. 우리는 클래스 유효성 검사기 유효성 검사 중 일부가 비동기 일 수 있기 때문 (약속 활용)에 이를 수행합니다.

다음으로 우리는 메타 타입 필드 (ArgumentMetadata에서 이 멤버를 추출)를 metatype 파라미터로 추출하기 위해 구조 조정을 사용하고 있습니다. 이것은 완전한 ArgumentMetadata를 얻은 다음 메타 타입 변수를 할당하기 위한 추가 명령문을 갖는 것에 대한 간단한 설명입니다.

다음으로 헬퍼 함수 toValidate()에 주목하십시오. 처리중인 현재 인수가 기본 JavaScript 유형인 경우 유효성 검증 단계를 생략해야 합니다 (스키마를 첨부 할 수 없으므로 유효성 검증 단계를 통해 스키마를 실행할 이유가 없습니다).

다음으로 클래스 변환기 함수 plainToClass()를 사용하여 일반 JavaScript 인수 객체를 유형이 지정된 객체로 변환하여 유효성 검사를 적용 할 수 있습니다. 네트워크 요청에서 역 직렬화되는 수신 본문에는 유형 정보가 없습니다. 클래스 유효성 검사기는 이전에 DTO에 대해 정의한 유효성 검사 데코레이터를 사용해야 하므로 이 변환을 수행해야 합니다.

마지막으로 앞에서 언급했듯이 유효 파이프(Validation Pipe)이므로 변경되지 않은 값을 반환하거나 예외를 발생시킵니다.

마지막 단계는 ValidationPipe를 바인딩하는 것입니다. 예외 필터와 유사한 파이프는 분석법 범위, 컨트롤러 범위 또는 전역 범위일 수 있습니다. 또한 파이프를 매개 변수 범위로 지정할 수 있습니다. 아래 예에서 파이프 인스턴스를 경로 매개 변수 @Body()데코레이터에 직접 연결합니다.

@@filename(cats.controller)
@Post()
async create(
  @Body(new ValidationPipe()) createCatDto: CreateCatDto,
) {
  this.catsService.create(createCatDto);
}

매개 변수 범위 파이프는 유효성 검증 로직이 하나의 지정된 매개 변수에만 관련될 때 유용합니다.

또는 메소드 레벨에서 파이프를 설정하려면 @UsePipes()데코레이터를 사용하십시오.

@@filename(cats.controller)
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

info 힌트 @UsePipes()데코레이터는@nestjs/common 패키지에서 가져옵니다.

위의 예에서 ValidationPipe인스턴스가 즉시 생성되었습니다. 또는 클래스 (인스턴스 아님)를 전달하여 인스턴스화를 프레임 워크에 남겨두고 종속 주입을 활성화합니다.

@@filename(cats.controller)
@Post()
@UsePipes(ValidationPipe)
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

ValidationPipe는 가능한 한 일반적으로 만들어 졌으므로 전체 응용 프로그램의 모든 경로 처리기에 적용되는 global-scoped 파이프로 설정해 봅시다.

@@filename(main)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

warning 알림 하이브리드 앱의 경우 useGlobalPipes()메소드는 게이트웨이 및 마이크로 서비스에 대한 파이프를 설정하지 않습니다. "표준"(하이브리드가 아닌) 마이크로 서비스 앱의 경우 useGlobalPipes()는 파이프를 전체적으로 마운트합니다.

전역 파이프는 모든 컨트롤러와 모든 경로 처리기에 대해 전체 응용 프로그램에서 사용됩니다. 의존성 주입의 관점에서, 모듈 외부에서 등록된 전역 파이프 (위의 예에서와 같이 useGlobalPipes()로)는 모듈의 컨텍스트 밖에서 수행되기 때문에 의존성을 주입할 수 없습니다. 이 문제를 해결하기 위해 다음 구성을 사용하여 모든 모듈에서 직접 글로벌 파이프를 설정할 수 있습니다.

@@filename(app.module)
import { Module } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

info 힌트 파이프에 대한 의존성 주입을 수행하기 위해 이 접근 방식을 사용할 때, 이 구성이 사용되는 모듈에 관계없이 파이프는 실제로 글로벌입니다. 어디에서 해야 합니까? 파이프 (위의 예에서는 ValidationPipe)가 정의된 모듈을 선택하십시오. 또한 커스텀 프로 바이더 등록을 다루는 유일한 방법은 useClass가 아닙니다. 여기에 대해 자세히 알아보십시오.

Transformation use case

파이프(Pipes)의 유일한 사용 사례는 아닙니다. 이 장의 시작 부분에서 파이프는 입력 데이터를 원하는 출력으로 변환 할 수 있다고 언급했습니다. transform 함수에서 반환된 값이 인수의 이전 값을 완전히 무시하기 때문에 가능합니다. 언제 유용한가요? 경우에 따라 클라이언트에서 전달된 데이터는 경로 처리기 메서드에서 올바르게 처리하기 전에 문자열을 정수로 변환하는 것과 같이 약간의 변경이 필요합니다. 또한 일부 필수 데이터 필드가 누락되어 기본값을 적용하려고합니다. 변환기 파이프는 클라이언트 요청과 요청 핸들러 사이에 처리 기능을 삽입하여 이러한 기능을 수행 할 수 있습니다.

다음은 문자열을 정수 값으로 구문 분석하는 ParseIntPipe입니다.

@@filename(parse-int.pipe)
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;
  }
}
@@switch
import { Injectable, BadRequestException} from '@nestjs/common';

@Injectable()
export class ParseIntPipe {
  transform(value, metadata) {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}

이 파이프를 아래 그림과 같이 선택한 매개 변수에 간단히 연결할 수 있습니다.

@@filename()
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}
@@switch
@Get(':id')
@Bind(Param('id', new ParseIntPipe()))
async findOne(id) {
  return await this.catsService.findOne(id);
}

원하는 경우 문자열을 구문 분석하고 UUID인지 확인하는 ParseUUIDPipe를 사용할 수 있습니다.

@@filename()
@Get(':id')
async findOne(@Param('id', new ParseUUIDPipe()) id) {
  return await this.catsService.findOne(id);
}
@@switch
@Get(':id')
@Bind(Param('id', new ParseUUIDPipe()))
async findOne(id) {
  return await this.catsService.findOne(id);
}

info 힌트 ParseUUIDPipe()를 사용할 때 버전 3, 4 또는 5에서 UUID를 구문 분석하는 중입니다. 특정 버전의 UUID 만 필요한 경우 파이프 옵션에서 버전을 전달할 수 있습니다.

이를 사용하면 요청이 해당 핸들러에 도달하기 전에 ParseIntPipe 또는 ParseUUIDPipe가 실행되어 id 매개 변수에 대해 항상 사용된 파이프에 따라 정수 또는 uuid를 수신합니다.

또 다른 유용한 경우는 id를 사용하여 데이터베이스에서 기존 사용자 엔티티를 선택하는 것입니다.

@@filename()
@Get(':id')
findOne(@Param('id', UserByIdPipe) userEntity: UserEntity) {
  return userEntity;
}
@@switch
@Get(':id')
@Bind(Param('id', UserByIdPipe))
findOne(userEntity) {
  return userEntity;
}

이 파이프의 구현은 독자에게 맡기지만 다른 모든 변환 파이프와 마찬가지로 입력 값 (id)을 수신하고 출력 값 (UserEntity 오브젝트)를 리턴합니다. 이를 통해 보일러 플레이트 코드를 처리기에서 일반 파이프로 추상화하여 코드를보다 선언적이고 건조하게 만들 수 있습니다.

The built-in ValidationPipe

다행스럽게도, ValidationPipeParseIntPipe는 Nest out-of-the-the-box에서 제공되므로 이 파이프를 직접 만들 필요는 없습니다. ( ValidationPipe에는 class-validatorclass-transformer패키지가 모두 설치되어 있어야 합니다.)

내장된 ValidationPipe는 이 장에서 구축한 샘플보다 더 많은 옵션을 제공합니다. 이 옵션은 파이프의 기본 메커니즘을 설명하기 위해 기본으로 유지되었습니다. 여기 예제가 많이 있습니다.

그러한 옵션 중 하나는 transform입니다. 역 직렬화된 바디 오브젝트에 대한 이전 논의는 바닐라 JavaScript 오브젝트 (DTO 유형이 없음)입니다. 지금까지 파이프를 사용하여 페이로드의 유효성을 검사했습니다. 이 과정에서 우리는 class-transform을 사용하여 평범한 객체를 유형이 지정된 객체로 임시 변환하여 유효성 검사를 수행할 수 있음을 상기할 수 있습니다. 내장된 ValidationPipe는 선택적으로 이 변환 된 객체를 반환할 수도 있습니다. 구성 객체를 파이프로 전달하여 이 동작을 활성화합니다. 이 옵션의 경우 아래와 같이 값이 truetransform필드가있는 구성 오브젝트를 전달하십시오.

@@filename(cats.controller)
@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

info 힌트 ValidationPipe@nestjs/common 패키지에서 가져옵니다.

이 파이프는 class-validatorclass-transformer라이브러리를 기반으로 하므로 사용 가능한 추가 옵션이 많이 있습니다. 위의 transform 옵션과 마찬가지로 파이프에 전달된 구성 객체를 통해 이러한 설정을 구성합니다. 다음은 기본 제공 옵션입니다.

export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}

이외에도 모든 클래스 유효성 검사기 옵션 (ValidatorOptions 인터페이스에서 상 속됨)을 사용할 수 있습니다.

Option

Type

Description

skipMissingProperties

boolean

true로 설정하면 유효성 검사기는 유효성 검사 개체에 없는 모든 속성의 유효성 검사를 건너 뜁니다.

whitelist

boolean

true로 설정하면 유효성 검사기는 유효성 검사 데코레이터를 사용하지 않는 속성의 유효성 검사 (반환 된) 개체를 제거합니다.

forbidNonWhitelisted

boolean

true로 설정하면 화이트리스트에 없는 속성을 제거하는 대신 유효성 검사기가 예외를 throw합니다.

forbidUnknownValues

boolean

true로 설정하면 알 수 없는 개체의 유효성 검사 시도가 즉시 실패합니다.

disableErrorMessages

boolean

true로 설정하면 유효성 검증 오류가 클라이언트에 리턴되지 않습니다.

exceptionFactory

Function

검증 에러의 배열을 취해, throw되는 예외 객체를 돌려줍니다.

groups

string[]

개체의 유효성을 검사하는 동안 사용할 그룹입니다.

dismissDefaultMessages

boolean

true로 설정하면 유효성 검사는 기본 메시지를 사용하지 않습니다. 명시 적으로 설정되지 않은 경우 오류 메시지는 항상 정의되지 않습니다.

validationError.target

boolean

ValidationError에 대상을 노출해야 하는지 여부를 나타냅니다.

validationError.value

boolean

ValidationError에 검증된 값을 표시해야 하는지 여부를 나타냅니다.

info 알림 class-validator 패키지에 대한 자세한 정보는 repository에서 찾으십시오.

Last updated