NestJs 스터디(6)

연꽃·2022년 2월 22일
0

NestJs

목록 보기
6/7
post-thumbnail

파이프(Pipe)

🏀 파이프란?

  • 파이프는 라우팅 핸들러가 실행되기 전에 개입하여, 데이터를 변환할 수 있다.

  • 파이프는 크게 변환, 유효성 검증의 역할을 한다.

    • 변환 (transformation) — 입력 데이터를 원하는 모양으로 변환한다. (타입 변환 등)
    • 유효성 검증 (validation) — 입력 데이터가 유효한지 평가하는데, 입력 데이터를 그대로 통과시키지만 데이터가 잘못되었으면 예외를 발생시킨다.

🏀 내장 파이프

  • Nest는 바로 사용할 수 있는 다음 여섯 가지 파이프를 내장하고 있다.
    • ValidationPipe
    • ParseIntPipe
    • ParseBoolPipe
    • ParseArrayPipe
    • ParseUUIDPipe
    • DefaultValuePipe
  • 이들은 @nestjs/common 패키지에서 export 할 수 있다.

🏀 파이프 등록

파이프를 사용하려면 파이프 클래스의 인스턴스나 클래스를, 사용하려는 컨텍스트에 바인드해야한다.

  • 라우팅 핸들러의 매개변수 수준으로 등록
    • 메서드 매개변수 수준에 등록
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

위 예시처럼 메스드 수준으로 등록이 가능하다. 핸들러가 실행되는 경우, id 매개변수는 반드시 숫자형이라는 것을 보증한다. 만약 GET localhost:3000/abc와 에서 abc와 같이, 숫자로 변환될 수 없는 매개변수가 들어오는 경우, 파이프 단에서 예외가 발생하여 다음 응답을 보내게 된다.

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}
  • 라우팅 핸들러의 매개변수 수준으로 등록
    • 쿼리 문자열 매개변수에 등록
      경로에서 매개변수를 얻는 @Param() 외에도, 쿼리 문자열에서 매개변수를 얻는 @Query()에도 적용이 가능하다.
@Get()
async findOne(@Query('id', ParseIntPipe) id: number) {
  return this.catsService.findOne(id);
}

추가적으로 @Param(), @Query() 외에도 @Body()을 통해 요청 페이로드 수준으로 등록이 가능하다.

  • 라우팅 핸들러 수준으로 등록
    @UsePipes() 데코레이터로 파이프를 라우팅 핸들러 자체에 등록하면, 해당 핸들러의 모든 매개변수에 대해 작동합니다.

  • 컨트롤러 수준으로 등록
    @UsePipes() 데코레이터로 컨트롤러에 등록하면, 해당 컨트롤러의 모든 핸들러의 모든 매개변수에 대해 작동합니다.

  • 전역으로 등록
    ValidationPipe같은 경우 최대한 범용적으로 쓸 수 있게 만들어진 덕분에 애플리케이션의 모든 라우팅 핸들러에 적용할 수 있습니다.

// main.ts

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

🏀 내장 변환 파이프 (Parse***Pipe)

  • 내장 변환 파이프는 입력받은 인수의 타입을 특정 타입으로 변환해준다.
  • 이는 반복되는 코드를 줄여주고, 메서드가 단일 책임 원칙에 부합하도록 해주는 편리한 기능이다.
  • 주로 라우팅 핸들러가 처리하기 전에 인수의 타입을 변경하는 등의 변화를 주고싶을 때 사용한다.

🏀 DefaultValuePipe

  • 변환 파이프는 인수가 존재해야한다. 만약 null이나 undefined를 입력받으면 예외가 발생한다.
  • 하지만 쿼리 문자열같은 경우 선택적으로 매개변수를 제공받고 싶은 경우,
    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 });
}

🏀 커스텀 파이프

  • 대부분의 경우 기본 제공하는 파이프로도 충분하지만, 커스텀 파이프를 만드는 것도 가능하다.

먼저 아래의 예시를 통해 받은 데이터를 그대로 보내주는 커스텀 파이프를 통해 간단해 구현해보자.

import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';

@Injectable()
export class PassthroughPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}

다음으로 ParseIntPipe를 만들어보자.

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;
  }
}

🏀 DTO 스키마 검증 파이프

  • DTO 스키마 검증 파이프는 요청 바디의 내용이 DTO 스키마에 반드시 부합하다고 보장하지 못하기 때문에 필요하다.
  • 그런데 파이프를 통하지 않더라도 다른 방법을 고려할 수 있는데 이 방법은 메서드 안에서 검증을 하거나, 데이터를 검증하는 미들웨어를 만드는 것이다.
  • 그런데 메서드 안에서 검증을 한다면 단일 책임 원칙(SRP)에 위배되며, 비슷한 메서드마다 똑같은 검증 코드가 반복될 수 있다.
  • 그리고 데이터를 검증하는 미들웨어의 경우, 전체 애플리케이션의 모든 컨텍스트에서 쓸 수 있는 미들웨어를 만드는 것은 불가능하다. 왜냐하면 미들웨어는 실행되는 컨텍스트나, 어떤 핸들러가 어떤 매개변수로 실행되는지 알지 못하기 때문이다.

🏀 Joi로 객체 스키마 검증

  • 객체를 DTO같은 스키마를 기반으로 검증하는 방법이 있는데 그 중 하나가 Joi 라이브러리는 쉬운 API와 간단한 방법으로 스키마를 생성하여 검증을 하는 방법이다.
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { ObjectSchema } from 'joi';

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

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

위 예시는 joi를 사용하는 검증이고, 파이프의 등록은 아래와 같다.

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

🏀 class-validator로 스키마 검증

  • class-validator 라이브러리는 데코레이터에 기반한 검증 방법을 제공한다.
  • 이 방법은 파이프의 생성자로 무엇도 필요하지 않고, 그저 인자 metadata의 metatype을 이용해 바로 검증한다.
  • class-validator를 사용하려면 class-validator, class-transformer 패키지를 설치해야 하며 TypeScript가 반드시 필요하다.

참고: 네스트 공식문서

profile
우물에서 자라나는 중

0개의 댓글