스프링에서 @Valid, @Validated을 이용한 검증 처리가 있다면, NestJS에서는 pipe를 이용하여 검증 처리를 할 수 있다.
공식 문서에는 아래와 같은 그림이 있다.
NestJS에서도 Filter가 존재한다. 하지만 스프링의 @Valid, @Validated와 더 유사하다고 생각한 데에는 이유가 있다.
filter는 요청에 대한 광범위한 오류를 잡아주는 역할을 해주고 pipe는 요청 데이터를 검증하여 유효하지 않으면 에러를 반환하는 역할이라는 차이점이 존재하기 때문이다.
https://stackoverflow.com/questions/63205074/nestjs-pipe-vs-filter
실제로 @Body, @Param 등의 데코레이터로 요청받은 데이터 형식이 적합한지, 데이터 값은 의도대로 들어오는지 확인하는 작업을 pipe로 할 수 있다. 이번 포스팅에서는 pipe에 대해 알아볼 것이다.
이론은 공식문서를,
코드는 John Ahn님의 `따라하면서 배우는 NestJS`(https://youtu.be/3JminDpCJNE) 강의를 참고했다.
Route Handler에서의 Pipe를 이용한 검증 처리
아래와 같은 라우트 핸들러 메서드가 있다.
board.controller.js 코드 일부
@Get('/:id')
getBoardById(@Param('id') id: string): Board {
return this.boardsService.getBoardById(id);
}
해당 메서드에는 id에 대한 검증 작업이 존재하지 않는다.
request에서 id가 올바른 형식(uuid)로 오는지 확인하지도 않으며, id가 올바른 데이터인지도 확인하지 않는다.
NestJS에서 Pipe는 1. 형식 체크, 2. 데이터 검증 역할을 담당해준다.
- transformation: transform input data to the desired form (e.g., from string to integer)
- validation: evaluate input data and if valid, simply pass it through unchanged; otherwise, throw an exception
즉, id가 올바른 형식(uuid)으로 오는지, id가 올바른 데이터인지 확인하기에 아주 적합한 존재라는 것이다!
참고로 Pipe의 동작 원리는 아래와 같이 공식문서에 소개돼있다.
In both cases, pipes operate on the arguments being processed by a controller route handler. Nest interposes a pipe just before a method is invoked, and the pipe receives the arguments destined for the method and operates on them.
출처: NestJS 공식문서
번역하자면, 파이프는 메서드 실행 직전에 요청 인수를 받아서 적절한 역할을 한다는 것.
파이프를 사용하기 위해선 아래와 같이 파라미터 옆에 파이프를 넣어주기만 하면 된다.
@Get('/:id')
getBoardById(@Param('id', ParseUUIDPipe) id: string): Board {
return this.boardsService.getBoardById(id);
}
@Param 데코레이터 옆에 ParseUUIDPipe가 추가된 것이 보이는가? 이렇게 체크할 인자 옆에 삽입만 해주면 된다.
인스턴스가 아닌 클래스(ParseUUIDPipe)를 전달만 하면 되는데, 그 이유는 인스턴스화에 대한 책임을 프레임워크에 맡기기 때문이다. 프레임워크가 종속성 주입을 활성화시켜주는 것.
In the example above, we pass a class (ParseIntPipe), not an instance, leaving responsibility for instantiation to the framework and enabling dependency injection.
출처: nestJS 공식문서
만약 반환할 statusCode를 400이 아닌 다른 코드로 커스텀하고 싶을 경우는 인스턴스를 전달하는 것이 효율적일 수 있다.
@Get('/:id')
getBoardById(
@Param('id', new ParseUUIDPipe({errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE})) id: string
): Board {
return this.boardsService.getBoardById(id);
}
id를 UUID 형식으로 요청보내지 않으면 406 에러를 반환하게 설정했다.
그리고 파라미터 뿐만 아니라 DTO 단에서도 검증을 할 수 있다.
이 점이 스프링의 @Valid, @Validated와 유사하다고 생각이 들었다.
터미널에 아래와 같이 class-validator, class-transformer를 설치해주자.
$ npm i --save class-validator class-transformer
이제 DTO 단에서 게시글의 제목 및 내용이 비어있으면 에러를 반환하게 해줄 수 있다.
그 전의 DTO에는 `title: string, description: string`이 전부였다면 이제 아래 코드처럼 class-validator에 속한 @IsNotEmpty() 와 같은 옵션을 추가해줄 수 있다.
create-board.dto.ts 코드 일부
import { IsNotEmpty } from "class-validator";
export class CreateBoardDto {
@IsNotEmpty()
title: string;
@IsNotEmpty()
description: string;
}
boards.controller.ts 코드 일부
@Post('/')
@UsePipes(ValidationPipe)
createBoard(@Body() createBoardDto: CreateBoardDto): Board {
return this.boardsService.createBoard(createBoardDto);
}
@UsePipes(ValidationPipe) 데코레이터를 이용하면 요청으로 들어온 DTO 클래스에 속한 validation 기능들을 활용할 수 있다.
참고로 NestJS에서는 ParseUUIDPipe 외에도 다양한 형식을 체크할 수 있도록 지원해준다.
아래와 같은 파이프 종류들을 지원해준다.
- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
- ParseFilePipe
Custom Pipe를 생성하여 원하는대로 값을 검증해주기
PipeTransform 클래스 구현체를 생성해주어 Custom한 pipe를 만들어줄 수 있다.
import { ArgumentMetadata, PipeTransform } from "@nestjs/common";
export class BoardStatusValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
}
}
여기서 value는 요청으로 들어온 값을, metadata는 요청인자에 대한 정보들을 가지고 있다.
transform 함수를 아래와 같이 설정해주어 커스텀한 pipe를 만들어줄 수 있다.
아래 함수는 게시글의 상태를 PUBLIC, PRIVATE 외에 다른 값으로 요청이 들어올 경우 예외를 반환하도록 하는 Pipe를 만들어준 것이다.
BoardStatusValidationPipe 클래스 코드 중 transform 함수
transform(value: any) {
value = value.toUpperCase();
if (value !== 'PRIVATE' && value !== 'PUBLIC') {
throw new BadRequestException(`올바르지 않은 게시글 상태입니다. status=${value}`)
}
return value;
}
이제 이 BoardStatusValidationPipe 클래스를 위에서 설명한 것과 같이 라우트 핸들러 메서드 인자 옆에다가 넣어주기만 하면 끝!
@Patch('/:id/status')
updateBoardStatus(
@Param('id', ParseUUIDPipe) id: string,
@Body('status', BoardStatusValidationPipe) status: BoardStatus
): Board {
return this.boardsService.updateBoardStatus(id, status);
}
참고자료
- nestjs 공식문서 https://docs.nestjs.com/pipes
- 따라하면서 배우는 NestJS https://youtu.be/3JminDpCJNE
'JS > Nest.js' 카테고리의 다른 글
[NestJS] 간단한 게시판 CRUD 구현하기 (2) | 2023.01.13 |
---|---|
[NestJS] NestJS 설치 및 애플리케이션 구축해보기 (0) | 2023.01.13 |