[NestJS] class-validator, class-transformer로 API parameter 제어하기
문제 상황
- 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;
}
- 결과
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
}
문제가 발생했다.
- nest app의 global 단위에 붙였던 val pipe + route 단위에 붙인 val pipe 두번 동작해서 디버깅용 로그가 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개가 동시에 해결되었다.
- 불필요한 ValdiationPipe 의 중복을 제거 (global level, route level)
- global level에서 validate -> route level에서 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