상세 컨텐츠

본문 제목

[NestJS] class-validator, class-transformer로 API parameter 제어하기

Development

by 12기통엔진 2024. 4. 19. 17:22

본문

반응형

문제 상황


  • string type message를 body parameter로 받기
export class MessageCreateDto {
  message: string
}
  • message가 유효한지 검사
  • message를 DB에 저장하기 전 암호로 변환

 

시도 1. (실험) class-validator, class-transformer 데코레이터를 각각 만들어 DTO에 붙여보기


  • class-validator, class-transformer package 설치
npm i --save class-validator class-transformer
  • (main.ts) nest application 객체의 global level 에 validation pipe 삽입
...
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalPipes(new ValidationPipe()) // New!
  await app.listen(4000)
}
...
  • (controller.ts) 모든 route에서 transform을 허용하면 엄밀함에 문제가 생길 수 있으니 해당 route에만 transform 옵션 true인 validator를 따로 추가
...
  @Post()
  @UsePipes(new ValidationPipe({transform: true})) // New!
  create(@Body() messageCreateDto: MessageCreateDto) {
    return this.messagesService.createMessage(messageCreateDto)
  }
...
  • 간단한 validator, transformer를 붙여 테스트
export class MessageCreateDto {
  @IsString() // New!
  @Transform((params: TransformFnParams<string>) => {
    return textToCode(params.value)
  }) // New!
  message: string
}
  • (참고!) (.../transform-fn-params.interface.d.ts) TransformFnParams 에 type parameter 지정
...
export interface TransformFnParams<T> {
    value: T;
    key: string;
    obj: any;
    type: TransformationType;
    options: ClassTransformOptions;
}
  • 결과

test man 으로 넣으면
textToCode 함수의 결괏값으로 변환

 

textToCode 함수의 return '.-.-.- .---.-. ..-...- -.-.'  그대로 변환해준다.

이제 IsString()을 걷어내고 Custom validator를 붙여보자

 

시도 2. custom validator를 만들어 DTO에 붙이기


 

GitHub - typestack/class-validator: Decorator-based property validation for classes.

Decorator-based property validation for classes. Contribute to typestack/class-validator development by creating an account on GitHub.

github.com

  • ValidatorConstraintInterface를 구상한 class를 붙여야한다. 나는 util 폴더를 따로 만들어 class를 생성했다.
...
// New!
@ValidatorConstraint()
export class CheckValidText implements ValidatorConstraintInterface {
  validate(text: string) {
    return this.isValid(text)
  }
  private isValid(text: string): boolean {
    return ...
  }
}
...
  • CreateDto에 붙이기
export class MessageCreateDto {
  @IsString()
  @Validate(CheckValidText, {
    message: 'This message cannot be converted to code.',
  }) // New!
  @Transform((params: TransformFnParams<string>) => {
    return textToCode(params.value)
  })
  message: string
}

 

문제가 발생했다.

  1. nest app의 global 단위에 붙였던 val pipe + route 단위에 붙인 val pipe 두번 동작해서 디버깅용 로그가 2번 출력된다.
  2. @Transform과 @Validate의 순서를 아무리 변경해도 @Transform 데코레이터가 먼저 실행되어 원하는대로 동작하지 않는다.
 

ValidationPipe transforms before validating · Issue #2856 · nestjs/nest

Bug Report Current behavior If a property from a DTO has transformation and validation decorators the transformation decorator will be applied before the validation ones. Since the validators work ...

github.com

nestjs 개발자의 답변: 의도된 동작이다.

동일한 ValidationPipe 안에서는 @Transform 데코레이터가 반드시 먼저 실행된다고 한다.

Transform을 성공적으로 마친 뒤 Validation하는 것이 자연스러운 흐름이긴 할 것 같다.

그럼 input text의 유효성을 먼저 검사한 뒤 암호화해야하는 내 경우는 어떡하지...?..?

 

시도 3. custom transform pipe를 만들어 route level에 삽입


  • transform을 수행하는 pipe를 만들어 route level에 삽입하자. global level에서 validation을 마치고 변환하길 기대한다.
  • PipeTransform interface를 구상한 클래스를 만들자. 이 친구도 util 폴더 안에서 만들었다.
...
export class CodeTransformPipe // New!
  implements PipeTransform<MessageCreateDto, MessageCreateDto>
{
  transform(value: MessageCreateDto) {
    return ...
  }
  ...
}
...
  • (controller.ts) route level에서 기존의 Validation Pipe는 걷어내고 Custom Transform Pipe를 넣자.
  ...
  @Post()
  @UsePipes(new CodeTransformPipe()) // New!
  create(@Body() messageCreateDto: MessageCreateDto) {
    return this.messagesService.createMessage(messageCreateDto)
  }
  ...
  • 마지막으로 CreateDto의 Transform 데코레이터도 정리해주자.
...
export class MessageCreateDto {
  @IsString()
  @Validate(CheckValidText, {
    message: 'This message cannot be converted to code.',
  })
  // @Transform() 데코레이터 제거
  message: string
}
...

 

문제 2개가 동시에 해결되었다.

  1. 불필요한 ValdiationPipe 의 중복을 제거 (global level, route level)
  2. global level에서 validate -> route level에서 transform

(좌) validate 실패 (우) validate 성공
validate에 성공하여 transform까지 동작

아싸

 

남은 궁금증


  • pipe를 지정할 수 있는 위치가 global, controller, route 등 다양한데? -> pipe 끼리의 순서는 어떻게 되지?
    • 1st: global pipes
    • 2nd: controller-level pipes
    • 3rd: route-level pipes
    • 위 3개 level 끼리는 순서를 변경할 수 없고, 같은 level 안에서는 작성한 순서에 따라 pipe가 동작한다.
 

[Question] How control global and non-global Pipe order? · Issue #3108 · nestjs/nest

Question How control global and non-global Pipeline order? At my application I have global ValidationPipe added with useGlobalPipes and custom ParseDatePipe added with Query decorator. I want run P...

github.com

반응형

관련글 더보기