Pipe란?

@Injectable() 데코레이터로 주석이 달린 클래스이다. Data TransformationData Validation 위해서 사용한다. 컨트롤러 경로 처리기에 의해 처리되는 인수에 대해 작동한다.

 

request가 올 때, 바로 handler로 가지 않고 pipe를 거쳐간다. handler에 도달하기 전, Data Transformation과 Data Validation을 하기 위해서이다. 만약, 통과되지 않는다면 handler로 가지않고 Error를 발생시킨다.

 

Data Transformation?

입력 데이터를 원하는 형식으로 변환 (Ex, String to int)

만약, 숫자를 받길 원하는데 문자열 형식으로 온다면 파이프에서 자동으로 숫자로 변환한다.

 

Data Validation?

입력 데이터를 평가하고 유효한 경우 변경되지 않은 상태로 전달하면 된다. 그렇지 않으면 데이터가 올바르지 않을 때 예외를 발생시킨다. 예를들어, 입력 길이가 10 이하여야 하는데, 10자 이상이면 예외를 발생시킨다.

 

Pipe를 사용하는 법 (Binding Pipes)

사용방법은 3가지로 Handler-level Pipes, Parameter-level Pipes, Global-level Pipes가 있다. 

 

Handler-level Pipes

  @Post()
  @UsePipes(pipe)
  createBoard(@Body() CreateBoardDto: CreateBoardDto): Board {
    return this.boardsService.createBoard(CreateBoardDto);
  }

핸들러 레벨에서 @UsePipes() 데코레이터를 이용해서 사용할 수 있다. 이 파이프는 모든 파라미터에 적용이 된다. (CreateBoardDto 내의 속성)

 

Parameter-level Pipes

  @Post()
  createBoard(@Body(ParameterPipe) CreateBoardDto: CreateBoardDto): Board {
    return this.boardsService.createBoard(CreateBoardDto);
  }

파라미터 레벨의 파이프라는 이름처럼 특정한 파라미터에만 적용이되는 파이프이다. 위처럼 CreateBoardDto에만 적용이 된다.

 

Global Pipes

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

이름처럼 애플리케이션 레벨의 파이프이다. 클라이언트에서 들어오는 모든 요청에 적용이 된다. 가장 상단 파일인 main.ts에 넣어주면 작동한다.

 

Build-in Pipes

Nest.js에 기본적으로 사용할 수 있게 만들어 놓은 6가지의 파이프가 있다.

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • parseArrayPipe
  • parseUUIDPipe
  • DefaultValuePipe

네이밍만으로도 어떤 역할을 하는지 알 수 있게끔 되어있다. 예시로 ParseIntPipe를 사용해보자.

 

  @Get("/:id")
  getBoardById(@Param("id", ParseIntPipe) id: number): Board {
    return this.boardsService.getBoardById(id.toString());
  }

위와같이 작성된 parameter-level pipe 안에 Nest.js에서 제공하는 ParseIntPipe를 사용하여 number를 받도록 코드를 작성했다.

request

int 값을 보내야하지만, absd라는 문자열을 보낸 결과 아래와 같은 에러 메시지가 온다.

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

에러 메시지로 어떠한 request 오류가 있는지 한 번에 알 수 있도록 해준다. (편리하네)

 

 

파이프를 이용한 유효성 체크

  @Post()
  createBoard(@Body() CreateBoardDto: CreateBoardDto): Board {
    return this.boardsService.createBoard(CreateBoardDto);
  }

현재 게시판 서비스를 만들고 있는데, 게시판의 제목과 내용에 ""인 공백을 입력해도 post가 되는 문제가 있다. 파이프를 이용해서 게시물을 생성할 때 유효성을 체크하도록 구현해보려고 한다.

 

필요한 모듈

  • class-validator
  • class-transformer

위 두개의 모듈이 필요하다. 해당 모듈은 명령어를 통해 가져올 수 있다. 아래 명령어를 터미널에서 실행해주자.

npm install class-validator class-transformer --save

document 페이지는 링크에 있다. 문서를 읽어보면 @IsNotEmpty()라는 데코레이터를 사용하면 원하는 기능이 구현될 것 같다. 

 

import { IsNotEmpty } from "class-validator";

export class CreateBoardDto {
  @IsNotEmpty()
  title: string;

  @IsNotEmpty()
  description: string;
}

파라미터에 받아오는 Dto에 위와같이 @IsNotEmpty()를 추가해주었다.

 

  @Post()
  @UsePipes(ValidationPipe)
  createBoard(@Body() CreateBoardDto: CreateBoardDto): Board {
    return this.boardsService.createBoard(CreateBoardDto);
  }

controller에도 @UsePipes 데코레이터를 추가하여 Handler-level pipe를 추가해주었다. ValidationPipe는 built-in pipe로 유효성을 체크하겠다는 의미이다.

 

그렇다면, 테스트를 해보자.

입력없이 request

{ "message": [ "title should not be empty", "description should not be empty" ], "error": "Bad Request", "statusCode": 400}

성공적으로 핸들러에서 에러를 처리하는 모습을 볼 수 있다.

 

 

커스텀 파이프를 이용한 유효성 체크

구현 방법

PipeTransform interface를 새롭게 만들 커스텀 파이프에 구현해줘야 한다. PipeTransform interface는 모든 파이프에서 구현해줘야 하는 인터페이스이다. 해당 Interface는 transform() 메소드를 필요로 하는데, nest.js가 인자를 처리하기 위해서 사용된다.

 

transform 메소드

  • value
  • metadata

위 두개의 인자를 가진다. 해당 메소드에서 return된 값은 route 핸들러로 전해지고, 예외가 발생한다면 클라이언트에 바로 전달된다.

 

실제 사용

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

export class BoardStatusValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    console.log("value", value);
    console.log("metadata", metadata);
     
    return value;
  }
}

위와 같이 PipeTransform class를 만들고

  @Patch("/:id/status")
  updateBoardStatus(
    @Param("id") id: string,
    @Body("status", BoardStatusValidationPipe) status: BoardStatus
  ) {
    return this.boardsService.updateBoardStatus(id, status);
  }

위와 같이 사용하고 싶은 곳에 넣어주면 된다. 

직접 돌려보면, Pipe 코드를 읽어 log가 출력되는 것을 확인할 수 있다. 그렇다면, 현재 enum에 없는 값으로 update해도 작동하고 있는데 pipe를 통해 막아보자.

 

export class BoardStatusValidationPipe implements PipeTransform {
  readonly StatusOptions = [BoardStatus.PRIVATE, BoardStatus.PUBLIC];

  transform(value: any, metadata: ArgumentMetadata) {
    value = value.toUpperCase();

    if (!this.isStatusValid(value))
      throw new BadRequestException(`${value} isn't in the status`);

    return value;
  }

  private isStatusValid(status: any): boolean {
    const index = this.StatusOptions.indexOf(status);
    return index !== -1;
  }
}

코드를 위와같이 변경해주고 테스트해보자.

성공 실패

정상적으로 예외처리하는 것을 확인할 수 있다.

 

 

Reference

'Javascript' 카테고리의 다른 글

로그인 기능 구현  (0) 2024.07.21
Nest.js를 이용한 게시판 API 개발  (0) 2024.07.21
[Nest.js] Module  (0) 2024.06.11
ES6  (0) 2024.06.05
기본 Javascript 문법  (0) 2024.06.04

+ Recent posts