BackEnd/Nest.js

[Nest.js] 파이프와 유효성 검사

Grace 2023. 5. 16. 18:31

파이프

파이프는 요청이 라우터 핸들러로 전달되기 전에 요청 객체를 변환할 수 있는 기회를 제공합니다. 미들웨어의 역할과 비슷하지만 메들웨어는 애플리케이션의 모든 콘텍스트에서 사용하도록 할 수 없습니다. 미들웨어는 현재 요청이 어떤 핸들러에서 수행되는지, 어떤 매개변수를 가지고 있는지에 대한 실행 콘텍스트를 알지 못하기 때문입니다.

라우트 핸들러는 웹 프레임워크에서 사용자의 요청을 처리하는 엔드포인트마다 동작을 수행하는 컴포넌트를 말합니다. 라우트 핸들러가 요청 경로와 컨트롤러를 매핑해준다고 이해하면 됩니다.

파이프는 다음 두 가지 목적으로 사용됩니다.

  • 변환: 입력 데이터를 원하는 형식으로 변환. 예를 들어 /users/user/1 내의 경로 매개변수 문자열 1을 정수로 변환
  • 유효성 검사: 입력 데이터가 사용자가 정한 기준에 유효하지 않은 경우 예외 처리

@nest/common 패키지에는 여러 내장 파이프가 마련되어 있습니다.

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • DefaultValuePipe

ParseIntPipe, ParseBoolPipe, ParseArrayPipe, ParseUUIDPipe는 전달된 인수의 타입을 검사하는 용도입니다. /users/user/:id 엔드포인트에 전달된 경로 매개변수 id는 타입이 문자열입니다 .이를 내부에서는 정수로 사용하고 있다면 컨트롤러에서 id를 매번 정수형으로 변환해서 쓰는 것은 불필요한 중복 코드를 양산하게 되므로 @Param 데코레이터의 두 번쨰 인수로 파이프를 넘겨 현재 실행 콘텍스트에 바인딩할 수 있습니다.

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

id에 정수로 파싱이 가능하지 않은 문자를 전달하면 유효성 검사 에러가 발생하면서 에러 응답을 돌려줍니다. 또한 요청이 컨트롤러에 전달되지 않습니다.

클래스를 전달하지 않고 파이프 객체를 직접 생성하여 전달할 수도 있습니다. 이 경우는 생성할 파이프 객체의 동작을 원하는 대로 바꾸고자 할 때 사용합니다.

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

DefaultValuePipe는 인수의 값에 기본값을 설정할 때 사용합니다. 쿼리 매개변수가 생략된 경우 유용하게 사용할 수 있습니다. 유저 목록을 조회할 때 오프셋 기반 페이징을 사용하고 있다면 쿼리 매개변수로 offset과 limit을 받아 아래와 같이 작성할 수 있습니다.

@Get()
findAll(
  @Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
  @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
  console.log(offset, limit);
  
  return this.usersService.findAll();
}

유효성 검사 파이프 만들고 내부 구현 이해하기

Nest 공식 문서에는 @UserPipes 데코레이터와 joi 라이브러리를 이요하여 커스텀 파이프를 바인딩하는 방법을 설명하고 있습니다.https://docs.nestjs.com/pipes#object-schema-validation joi는 유효성 검사 라이브러리입니다. 스키마라고 부르는 유효성 검사 규칙을 가진 객체를 만들고 이 스키마에 검사하고자 하는 객체를 전달하여 평가하는 방식입니다. 하지만 joi는 스키마를 적용하는 문법이 번거롭습니다. 타입스크립트를 사용한다면 class-validator를 사용하면 좋습니다.

$ npm i --save class-validator class-transformer
// dto/create-user.dto
import { IsString, MinLength, MaxLength, IsEmail } from 'class-validator';

export class CreateUserDto {
  @IsString()
  @MinLength(1)
  @MaxLength(20)
  name: string;
  
  @IsEmail()
  email: string;
}

class-validator를 사용하면 다양한 데코레이터를 선언하여 쓰기도 쉽고 이해하기도 쉬운 코드를 작성할 수 있습니다. 이 코드에서 CreateUserDto의 name 속성은 1글자 이상 20글자 이하인 문자열을 받도록 되어 있습니다. email 속성은 이메일 형식을 따르는지 체크합니다.

이제 위에서 정의한 것과 같은 dto 객체를 받아서 유효성 검사를 하는 파이프를 구현합니다.

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any>{
  async transform(value: any, { metataype }: ArgumentMetadata) {
    if(!metatype || !this.toValidate(metatype)) {
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if(errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
  
  private toValidate(metatype: Function): boolean {
    const type: Funtion[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

먼저 전달된 metatype이 파이프가 지원하는 타입인지 검사합니다. 그리고 class-transformerprainToClass 함수를 통해 순수 자바스크립트 객체를 클래스의 객체로 바꿔줍니다. class-validator의 유효성 검사 데코레이터는 타입이 필요합니다. 네트워크 요청을 통해 들어온 데이터는 역직렬화 과정에서 본문의 객체가 아무런 타입 정보도 가지고 있지 않기 때문에 타입을 지정하는 변환 과정을 painToClass로 수행하는 것입니다. 마지막으로 유효성 검사에 통과했다면 원래의 값을 그대로 전달하고, 실패했다면 400 BadRequest 에러를 던집니다.

@Post()
create(@Body(ValidationPipe) createDto: CreateUserDto) {
  return this.usersService.create(createUserDto)
}

ValidationPipe를 모든 핸들러에 일일히 지정하지 않고 전역으로 설정하려면 부트스트랩 과정에서 적용하면 됩니다.

import { ValidationPipe } from './validation.pipe';

async function bootstrap() {
  const app = await NEstFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe())
  await app.listen(3000);
}

bootstrap();

validationPipe를 직접 만들어서 사용했지만 Nest에는 이미 있기 때문에 굳이 따로 만들지 말고 가져다 쓰도록 하세요.

'BackEnd > Nest.js' 카테고리의 다른 글

[Nest.js] 로깅  (0) 2023.05.30
[Nest.js] JWT 인증/인가  (0) 2023.05.26
[Nest.js] Config 패키지  (0) 2023.05.16
[Nest.js] 프로바이더  (0) 2023.05.16
[Nest.js] 인터페이스  (0) 2023.05.08