파이프는 일반적으로 다음 두 가지 사용 사례가 있다.
express에선 하나하나 미들웨어로 만들어야했다면 nest.js에선 자체적으로 제공하는 빌트인 파이프를 데코로 붙여서 사용하기만 하면 된다 (편-안)
예를 들어 게시글을 작성하는 api의 컨트롤러에서 입력받은 데이터를 검증하고자 한다면 다음과 같이 할 수 있다.
@Post()
@UsePipes(new ValidationPipe())
async createPost(
@Body() postContent: PostContentDto,
): Promise<PostDto> {
...
}
@Body 데코레이터는 HTTP 요청 본문에 있는 값을 메서드의 인자로 바인딩하는 역할을 하고 이 때 바인딩된 DTO 객체는 @UsePipes(new ValidationPipe()) 데코레이터에 의해 유효성 검사를 실행하기 된다.
즉, PostContentDto 클래스에 정의된 @IsString(), @IsNotEmpty() 등의 데코레이터에 부합하는지 검사하게 된다.
export class PostContentDto {
@ApiProperty()
@IsNotEmpty()
@IsString()
@Length(1, 50)
title: string;
@ApiProperty()
@IsNotEmpty()
@IsString()
@Length(1, 1000)
content: string;
}
express라면 입력받은 데이터를 가공하거나 형변환을 하려면 함수 내부에서 코드를 작성해야했더라면 nest.js에서는 마찬가지로 간단한 파이프 데코로 처리할 수 있어 보다 간결한 컨트롤러를 구현할 수 있다.
하지만 개인적인 생각으로 형변환에 관련된 빌트인 파이프들은 알맞게 바로 사용할 수 없는 경우가 많아서 직접 만들어서 쓴 경우가 많았던것 같다.
사용법은 다음과 같다.
@Post()
@UsePipes(new ValidationPipe())
@UseGuards(AuthGuard('jwt'))
async createComment(
@Request() req: RequestWithUser,
@Body() commentContent: CommentContentDto,
@Query('postId', ParseIntPipe) postId: number,
@Optional() @Query('parentId', OptionalIntPipe) parentId?: number,
): Promise<CommentDto> {
...
}
ParseIntPipe 는 전달받은 문자열인 postId를 10진수로 변환해주는 빌트인 파이프이다.
하지만 쿼리중 옵션으로 받게되는 parentId에도 ParseIntPipe를 적용하면 해당 값이 없는 경우 변환할 수 없으므로 예외를 발생시키게 된다. 이런 경우 파이프를 커스텀해서 사용해야한다.
import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
@Injectable()
export class OptionalIntPipe implements PipeTransform {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
transform(value: string, metadata: ArgumentMetadata) {
if (!value) return null;
const val: number = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed (numeric string is expected)');
}
return val;
}
}
만일 value가 null 또는 undefined라면 null을 반환하도록 하는데 이는 선택적인 값을 처리하기 위함이다.
value가 존재하면 해당 값을 10진수 숫자로 변환하고 반환한다. 만약 NaN이라면, 즉 숫자로 변환할 수 없는 문자열이라면 예외를 발생시키도록 만든 파이프이다.
파이프는 예외 구역 내부에서 실행된다. 이는 파이프가 예외를 발생시킬 때 예외 계층(전역 예외 필터 및 현재 컨텍스트에 적용되는 모든 예외 필터 ) 에 의해 처리된다는 것을 의미한다.
"예외 구역"은 파이프가 실행되는 코드 영역을 지칭한다. 파이프는 요청 처리 파이프라인의 일부로서, 데이터를 변환하거나 유효성을 검사하는 역할을 하게되고 이 과정에서 파이프 내부에서 예외가 발생하면 예외는 NestJS의 "예외 계층"에 의해 처리된다.
"예외 계층"이란 전역 예외 필터와 현재 컨텍스트(context)에 적용되는 모든 예외 필터를 포함하는 계층을 의미한다. 이 계층은 애플리케이션의 예외 처리 메커니즘을 담당하며, 파이프에서 발생하는 예외를 적절히 처리하여 사용자에게 알려준다.
즉, 파이프는 컨트롤러의 메서드가 실행되기 전 유효성 검사 및 형변환을 수행하고 이 과정에서 예외가 발생하게되면 컨트롤러 메서드는 실행되지 않는다.