NestJS 帶你飛! 시리즈 번역 09# Pipe (상)

2023. 6. 2. 22:33개발 문서 번역/NestJS

728x90
이 포스팅은 「NestJS 기초실무 가이드 : 강력하고 쉬운 Node.js 웹 프레임워크로 웹사이트 만들기」
(서명: NestJS 基礎必學實務指南:使用強大且易擴展的 Node.js 框架打造網頁應用程式)
책이 출간되었습니다. 

Pipe란 무엇인가요?

Pipe는 사용자가 입력한 파라미터를 처리하는데 사용됩니다.

(ex: 데이터 검증, 데이터 변경 등) 

요청이 라우터 핸들러로 전달되기 전 요청 객체를 변환하거나 검사할 수 있습니다.

미들웨어와 비슷하나 미들웨어를 현재 요청이 어떤 핸들러에서 수행되고 어떤 매개변수를 갖는지에 대한

실행 context를 알지 못하기 때문에 모든 context에서 사용이 불가능합니다.

손님이 주문한 후 웨이터가 메뉴를 다시 한번 더 검사하는것과 비슷합니다.

 

Nest Pipe

Nest에서 Pipe는 Exception의 예외 처리를 지원합니다. Pipe에서 Exception을 던졌을 경우

이 요청은 Controller의 해당 메서드까지 가지 않습니다.
이렇게 설계하게 되면 검증을 담당하는 부분과 실행하는 부분을 효과적으로 분리할 수 있습니다.

Nest는 몇가지의 내장 Pipe를 통해 자료의 유형 변경 혹은 검증을 도와줍니다.

 

  • ValidationPipe: 자료의 양식을 검사합니다. 
  • ParseIntPipe: 해당 인자가 Integer형인지를 검사하는 Pipe입니다.
  • ParseBoolPipe: 해당 인자가 Boolean형인지를 검사하는 Pipe입니다.
  • ParseArrayPipe: 해당 인자가 Array형인지를 검사하는 Pipe입니다.
  • ParseUUIDPipe: 해당 인자가 UUID의 형식인지를 검사하는 Pipe 입니다.
  • DefaultValuePipe: 해당 자료의 기본값을 설정할 때 사용되는 Pipe입니다.

 

Pipe 사용하기

Pipe를 사용하는 방법은 간단합니다. 라우터 파라미터의 자료형이 Integer임을 해석/검증 하고 싶다면

@Param 데코레이터를 추가하고 라우터 파라미터에 ParseIntPipe를 연결하면 됩니다.

아래 app.controller.ts를 예로 들어보겠습니다.

id를 해석시 그 id의 값이 Integer이라면 AppService에서 해당하는 User의 정보를 받아오게 하고

그 정보가 없다면 Exception을 발생시키는 예제입니다.

import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(@Param('id', ParseIntPipe) id: number) {
    return this.appService.getUser(id);
  }

}

app.service.ts를 수정 해봅시다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {

  getUser(id: number) {
    const users = [
      {
        id: 1,
        name: 'WOO'
      }
    ];
    const user = users.find(x => x.id === id);
    return user || {};
  }
}

웹 브라우저를 통해 http://127.0.0.1:3000/WOO에 들어가보면 오류 메세지가 출력된것을 확인할 수 있습니다.

WOOInteger(정수) 형식이 아닌 문자열이니까요.

{
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

 

내장 Pipe 커스텀 HttpStatusCode (Built-In Pipe Custom Http Code, 內建Pipe 自訂 HttpStatusCode)

오류 메세지를 수정하고 싶다면 ParseIntPipe에선 반드시 인스턴스화를 해야하고 관련 파라미터를 입력해야 합니다.

아래는 app.controller.ts의 예로 예제에서 발생시키고 싶은 HttpStatusCode는 406입니다.

import { Controller, Get, HttpStatus, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
    id: number
  ) {
    return this.appService.getUser(id);
  }

}

http://127.0.0.1:3000/WOO 에 접속해볼까요?

{
  "statusCode": 406,
  "message": "Validation failed (numeric string is expected)",
  "error": "Not Acceptable"
}

위와 같이 상태 코드가 406으로 바뀐것을 알 수 있습니다.

 

내장 Pipe 커스텀 Exception

 

오류 메세지를 변경하고 싶다면 exceptionFactory 파라미터를 통해 발생시킬 Exception을 지정할 수 있습니다.

아래는 app.controller.ts의 예제입니다.

import { Controller, Get, NotAcceptableException, Param, ParseIntPipe } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param(
      'id',
      new ParseIntPipe({
        exceptionFactory: () => new NotAcceptableException('숫자가 아닌 값은 변환할 수 없습니다.')
      })
    )
    id: number
  ) {
    return this.appService.getUser(id);
  }

}

웹 브라우저를 열고 http://127.0.0.1:3000/WOO에 접속해보면...

{
  "statusCode": 406,
  "message": "숫자가 아닌 값은 변환할 수 없습니다.",
  "error": "Not Acceptable"
}

커스텀 Pipe

 

내장 Pipe가 맘에 드는게 없을 경우에는 Nest에서 커스텀 Pipe를 지정할 수 있습니다.

사실 Pipe는 @Injectableclass이며 PipeTransform 인터페이스를 구현해야 합니다.

또한 CLI를 통해 Pipe를 생성할 수 있습니다.

$ nest generate pipe <PIPE_NAME>
주의: <PIPE_NAME>은 경로를 포함할 수 있습니다. ex: pipes/parse-int
이럴 경우 src 디렉토리 아래에 경로를 포함한 Pipe가 생성됩니다.

ParseIntpipepipes의 폴더 아래에 생성 해보겠습니다.

$ nest generate pipe pipes/parse-int

src 아래 pipes라는 폴더가 보일겁니다.

폴더 안에는 parse-int.pipe.tsparse-int.pipe.spec.ts가 생성이 된걸 확인할 수 있습니다.

아래는 Pipe의 뼈대입니다.
transform(value: any, metadata: ArgumentMetadata) 메소드를 확인할 수 있습니다.

이는 로직을 판단하는 곳이며 이 value는 들어온 값, metadata는 처리하고 있는 파라미터의 메타데이터(데이터의 데이터) 입니다.

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

@Injectable()
export class ParseIntPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
주의: PipeTransform 뒤에는 2개의 Type을 추가할 수 있습니다.
첫번째는 T, 들어와야 하는 값이 어떤 유형이어야 하는가를 정의합니다. transform 안의 value입니다.
두번째는 R, 반환시의 자료형입니다.

parse-int.pipe.ts를 조금 수정해보겠습니다. parseInt를 거친 후의 valueNaN인지,
NaN라면 NotAcceptableException을 던지겠습니다.

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

@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
  transform(value: string, metadata: ArgumentMetadata) {
    const integer = parseInt(value);
    if ( isNaN(integer) ) {
      throw new NotAcceptableException('숫자가 아닙니다.');
    }
    return integer;
  }
}

다음으로 app.controller.ts를 수정 해서 우리가 만든 ParseIntPipe 커스텀 파이프를 적용시켜 보겠습니다.

import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { ParseIntPipe } from './pipes/parse-int.pipe';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  getUser(
    @Param('id', ParseIntPipe) id: number
  ) {
    return this.appService.getUser(id)
  }

}

브라우저를 열고 http://127.0.0.1:3000/WOO에 접속하면 아래와 같은 결과를 확인할 수 있습니다.

{
  "statusCode": 406,
  "message": "숫자가 아닙니다.",
  "error": "Not Acceptable"
}

 

마치며

Pipe는 자료의 유효성 검사에서 실용적인 기능중 하나입니다. 객체 클래스의 자료는 어떻게 검증할까요?

이 부분은 다음 포스팅에서 설명하도록 하겠습니다. 오늘의 학습 요약본입니다.

 

1. Pipe는 자료의 유효성 검증 및 자료형 변환에 쓰입니다.

2. Nest는 6개의 내장 Pipe를 제공합니다.

3. 내장 Pipe는 Http Staus Code와 Exception을 커스텀할 수 있습니다.

4. Pipe는 @Injectableclass이며 PipeTransform 인터페이스를 구현해야 합니다.