개발일지
[NestJS] ValidationPipe 커스텀하기 본문
이전에 구현했던 프로젝트 기준으로 설명하겠습니다.
🔗 [NestJS] 회원가입 구현 #1 - Pipe 적용
🔗 [NestJS] 예외 처리와 exception filter
Dto 속성의 유효성 검사를 위해 ValidationPipe
와 class-validator
를 사용했었습니다. 그런데 class-validator
를 사용하면 에러 메시지가 배열 형태로 반환됩니다. 이를 exception filter에 적용한 것과 동일한 형태로 반환하고싶었기에 기존의 ValidationPipe
를 커스텀했습니다.
ValidationPipe
내부 살펴보기
ValidationPipe
내부를 살펴보면 createExceptionFactory()
메소드가 존재하고, 함수 내의 flattenValidationErrors()
메소드 내에서 모든 validation 에러 메시지를 배열 형태로 변환하는 것을 알 수 있습니다. 더 자세한 내용은 여기를 참고해주세요.
public createExceptionFactory() {
return (validationErrors: ValidationError[] = []) => {
if (this.isDetailedOutputDisabled) {
return new HttpErrorByCode[this.errorHttpStatusCode]();
}
const errors = this.flattenValidationErrors(validationErrors);
return new HttpErrorByCode[this.errorHttpStatusCode](errors);
};
}
...
protected flattenValidationErrors(
validationErrors: ValidationError[],
): string[] {
return iterate(validationErrors)
.map(error => this.mapChildrenToValidationErrors(error))
.flatten()
.filter(item => !!item.constraints)
.map(item => Object.values(item.constraints))
.flatten()
.toArray();
}
모든 에러 메시지를 배열에 담기보다는 제일 첫번째에 해당하는 에러 메세지만을 에러 코드와 함께 반환하고 싶었습니다. 따라서 flattenValidationErrors()
를 사용하지 않고 원하는 형태로 변환해주는 메소드를 정의해주었습니다.
ValidationPipe
커스텀 하기
decorator에 context 전달하기
class-validator에서는 유효성 검사에 실패했을 경우의 속성들을 데코레이터에 지정할 수 있도록 합니다.
🔗 Passing context to decorators
Dto에 다음과 같이 에러 코드를 정의합니다.
export class SignUpUserDto {
@IsEmail(
{},
{
context: {
code: 'MUST_EMAIL_TYPE',
},
},
)
@IsNotEmpty({
context: {
code: 'EMPTY_EMAIL',
},
})
email: string;
@IsString({
context: {
code: 'MUST_STRING_TYPE',
},
})
@IsNotEmpty({
context: {
code: 'EMPTY_NICKNAME',
},
})
nickname: string;
...
}
validationPipe 커스텀하기
기존의 ValidationPipe
을 상속받는 CustomValidationPipe
를 만들고 createExceptionFactory()
메소드를 오버라이딩 해줬습니다. getExceptionObj()
로 { code, message }를 갖는 에러 객체를 반환한 뒤 exception filter를 거치기위해 HttpException 에러를 던집니다.
// validation.pipe.ts
import {
HttpException,
ValidationError,
ValidationPipe as NestValidationPipe,
} from '@nestjs/common';
import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util';
import { ExceptionObj, OthersException } from '@vote/middleware';
import { CustomException } from '../exception.filter/http-exception.filter';
export class CustomValidationPipe extends NestValidationPipe {
public createExceptionFactory() {
return (validationErrors: ValidationError[] = []) => {
if (this.isDetailedOutputDisabled) {
return new HttpErrorByCode[this.errorHttpStatusCode]();
}
const errors = this.getExceptionObj(validationErrors); // { message, code } 반환
return new HttpException(errors, 400);
};
}
}
getExceptionObj()
메소드 입니다. 제일 처음 발생된 에러 객체(validationErrors[0])에서 기존의 에러 메시지(message)와 위에서 지정한 에러 코드(code)를 가져온 뒤 객체 형태로 반환합니다. 에러 코드가 지정되어있지 않을 경우 에러 처리됩니다.
protected getExceptionObj(validationErrors: ValidationError[]): ExceptionObj {
const error = validationErrors[0];
// get code
const errorCode = error.contexts;
const key = errorCode ? Object.keys(errorCode)[0] : undefined;
const code = errorCode?.[key]['code'];
if (code === undefined) {
throw new CustomException(OthersException.NOT_VALIDATION_ERROR_CODE);
}
// get message
const message = error.constraints?.[key];
return {
code,
message,
};
}
구현한 CustomValidationPipe
를 기존의 파이프 대신 사용할 수 있도록 설정해줍니다.
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new CustomValidationPipe());
...
await app.listen(configService.get('PORT'));
}
bootstrap();
exception filter 수정하기
controller 또는 service에서 에러가 발생했을 때와는 다르게 위에서 정의된 validation 에러의 경우 error stack 없이 바로 response 객체를 반환합니다. 따라서 두 경우의에 맞게 데이터를 받아올 수 있도록 했습니다.
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const httpRes = host.switchToHttp().getResponse<Response>();
const status = exception.getStatus();
const response = exception.getResponse();
const errorRes = response?.['stack'] ? response?.['response'] : response;
return httpRes.status(status).json({
error: {
code: errorRes?.['code'] || status,
message: errorRes?.['message'],
},
});
}
}
결과
커스텀 전

커스텀 후

'NestJS, Node.js > #01 Project - 투표 커뮤니티' 카테고리의 다른 글
[NestJS] TypeORM 적용하기 (0) | 2022.12.22 |
---|---|
[NestJS] S3로 이미지 파일 관리하기 (0) | 2022.12.09 |
[NestJS] Prisma 환경 변수 문제 해결하기 (0) | 2022.11.16 |
[NestJS] 환경변수 설정하기 - config, cross-env, joi (0) | 2022.11.16 |
[NestJS] 로그아웃 및 비밀번호 재설정 (0) | 2022.11.14 |