Nestjs를 배워보자 7일차 - Custom Pipe

0

Nestjs

목록 보기
7/9

Nest

본 강의는 'john ahn'님의 강의를 정리한 내용입니다.
https://www.youtube.com/watch?v=3JminDpCJNE

1. 커스텀 파이프

이전 시간에는 pipe에 대해서 알아보았다. 이전에는 빌트인 파이프만을 사용했다면 이번에는 커스텀 파이프를 구현해볼 것이다.

우선 커스텀 파이프를 구현하기 위해서는 PipeTransform이라는 인터페이스를 새롭게 만들 커스텀에 implements 해주어야 한다. 이 PipeTransform 인터페이스는 모든 파이프에서 구현해주어야 하는 인터페이스로, transform() 메서드를 구현해주어야 한다. transform() 메서드가 바로 pipe가 라우팅 핸들러로 인자를 보내기전에 작업을 메서드라고 생각하면 된다.

다음과 같은 예시가 있다.

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

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

공식문서에서 발췌한 내용으로, PipeTransform 인터페이스를 implements해주고 , transform 메서드를 구현해주는 모습을 볼 수 있다.

그런데, 인자로 사용되는 value와 metadata는 무엇일까??

transform()메서드
첫번쨰 파라미터는 처리가 된 인자의 값이다.
두번째 파라미터는 인자에 대한 메터 데이터를 포함한 객체이다.

또한, transform() 메서드에서 return 된 값은 Route 핸들러로 전해진다. 만약 예외가 발생하면 클라이언트에 바로 던져진다.

그러나 value와 metadata가 무엇인지 아직 모르겠다. 그러므로 콘솔로 직접 찍어보도록 하자

먼저 커스텀 파이프를 만들어보자
boards 폴더에 pipes라는 폴더를 만들고 board-status-validation.pipe.ts 파일을 만들자.

boards/pipes/board-status-validation.pipe.ts 가 되는 것이다.

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

해당 custom pipe를 이용하여 우리의 board의 status를 검증해보자

만든 커스텀 파이프를 넣어주는 방법은 다음과 같다.
먼저 Controller에 가보자

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

@Body()의 두번째 인자로 커스텀 파이프를 넘겨준 것이다. 이는 파이프 적용 단계 중에 파라미터 단계에 속한다. 어차피 우리는 status만 검증할 것이기 때문이다.

이제 postman으로 실험해보자

console로 찍은 값이 이렇게 나올 것이다.

value board1
metadata { metatype: [Function: String], type: 'body', data: 'status' }

즉, 첫번째 인자는 우리가 pipe로 집어넣은 부분의 파라미터 값이다. 현재는 파라미터 레벨로 파이프를 적용시켰는데, 만약 메서드 레벨로 적용했다면 body의 모든 값들이 value로 들어왔을 것이다. (단, 메서드 레벨로 적용했어도 파라미터로 받는 @Body() 가 특정 값을 지칭할 때, 가령 @Body('id')이면 value는 id만 받는다.)

두번째 인자는 정말 해당 body의 메타데이터, 즉 헤더 부분이라고 생각하면 된다.

이제 커스텀 파이프를 구현하여, 상태(status)가 PUBLIC, PRIVATE만 올 수 있도록 만들어보자

  • board-status-validation.pipe.ts
import { ArgumentMetadata, BadRequestException, PipeTransform } from "@nestjs/common";
import { BoardStatus } from "../boards.model";

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 options`)
        }
        return value;
    }

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

readonly라고 써주면, 외부 클래스에서 해당 값을 읽을 수는 있지만 ,수정은 불가능하다. 우리는 해당 배열의 값들을 기준으로 삼고, 배열의 값이 없으면 false 있으면 true를 내주고 싶은 것이다.

그래서 isStatusValid 메서드를 만들어서 StatusOptions배열안에 해당 값이 있는 지, 없는 지 indexOf 로 확인한다. transform 메서드에서는 value 데이터를 받고, 대문자로 만들어준 다음 StatusOptions에 값이 없다면 잘못된 요청으로 상태코드 400을 반환한다.

정상이라면 그대로 value를 리턴하여 controller 핸들러에 전달한다.

다음의 코드를 넣어주고 postman을 실행해보자,

실패할 경우 다음과 같이 BadRequest에 적어준 오류 내용이 메시지로 반환된다.

상태 값을 제대로 입력하면 다음과 같이 제대로된 리턴값이 나온다.

2. 메서드 레벨에서의 커스텀 파이프

지난 시간에 빌트인 파이프를 이용해서, 있어야 하는 값이 반드시 파라미터로 전송되었는 지 체크해보았다.

boardDTO를 보면

import { IsNotEmpty } from 'class-validator'

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

    @IsNotEmpty()
    description : string;
}

다음과 같이 되어있고, @IsNotEmpty()는 없어서는 안된다는 의미이다. 이거를 빌트인 pipe가 아닌 커스텀 파이프를 이용해서 구현해보도록 하자

먼저 pipes/board-status-validation.pipe.ts 파일을 만들도록 하자
그리고 다음의 코드를 쓰도록 하자

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

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

해당 파이프는 controller의 createBoardDto() 메서드의 pipe로 사용될 커스텀 파이프이다. 아직 메서드 레벨로 인자를 받으면 어떻게 올지 모르니 console.log로 찍어보도록 하자

@Post()
@UsePipes(BoardCreateValidationPipe)
createBoard(
    @Body() createBoardDto : CreateBoardDto 
) : Board {
    return this.boardService.createBoard(createBoardDto);  
}

다음과 같이 UsePipes()를 이용하여 파이프를 설정해줄 수 있다.

다음과 같은 결과가 나올 것이다.

{ title: 'board1', description: 'hello' }

즉, 파라미터 레벨에서 Body()로 특정 파라미터만을 지칭하면 해당 파라미터 값만 파이프의 value로 들어갔다. 그러나, 메서드 레벨에서 파이를 설정하고 Body()의 모든 값을 받으면 value에 json 형식으로 모든 값이 들어감을 알 수 있다.

이제 title과 description의 값이 없는 경우를 처리해주도록 하자

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

export class BoardCreateValidationPipe implements PipeTransform {
    transform(value : any, metadata : ArgumentMetadata){
        const title = value.title as string
        const desc = value.description as string

        if( title === "" || desc === "" || title === undefined || desc === undefined){
            throw new BadRequestException("Wrong input value")
        }
        return value;
    }
}

다음과 같이하여 값이 없거나 아예 title, desc가 없을 경우를 처리해주도록 하자


잘 처리해주는 것을 확인할 수 있다.

0개의 댓글