개발자가 되고 싶은 개발자

class-validator 사용법 본문

Dev/JavaScript & TypeScript

class-validator 사용법

Fullth 2024. 8. 15. 16:45

Photo by Samsung Memory on unsplash

Preface

  • 이전 포스팅을 작성하게된 본 목적을 이어서 작성합니다.
  • 동료분이 노드 관련해서 질문주셔서 문제점을 같이 찾아보다가 class-validator의 옵션이 원인이었고,
    몰랐던 옵션에 대해 알게되었습니다.
  • 해당 라이브러리에 대한 사용법과 옵션에 대해서 간단하게 정리합니다.
 

class-transformer란 무엇이고 왜 사용할까?

Preface동료분이 노드 관련해서 질문주셔서 문제점을 같이 찾아보다가 class-validator의 옵션이 원인이었고, 몰랐던 옵션에 대해 알게되었습니다. 해당 옵션만 정리하려다가 TypeStack의 라이브러리를

fullth.tistory.com

TypeStack

  • class-validator도 class-transformer과 동일하게 TypeStack의 프로젝트입니다.
  • 스프링처럼 Dependenchy Inejction을 지원하는 typedi와 같은 여러 어노테이션 기반의 타입 프로젝트들을 진행하고 있습니다.
 

TypeStack

Decorator based frameworks and libraries for Node and browser. - TypeStack

github.com

typestack/class-validator

  • class-validator는 데코레이터 기반의 클래스 속성 유효성 검사 라이브러리입니다.
  • 클라이언트로부터 전달된 객체에 대한 값을 검사할 수 있습니다.
  • 자릿수, 필수값, 타입 검사 등 여러 옵션들이 존재합니다.
Decorator-based property validation for classes.
 

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

Usage

  • 아래와 같이 검사할 속성에 대해서 어노테이션을 작성해줍니다.
  • 그리고 클래스의 객체를 생성하거나, 이전 포스팅에서 작성한 것처럼 전달받은 값을 class-transformer를 통해서 인스턴스화 시켜줍니다.
import {
  validate,
  validateOrReject,
  Contains,
  IsInt,
  Length,
  IsEmail,
  IsFQDN,
  IsDate,
  Min,
  Max,
} from 'class-validator';

export class Post {
  @Length(10, 20)
  title: string;

  @Contains('hello')
  text: string;

  @IsInt()
  @Min(0)
  @Max(10)
  rating: number;

  @IsEmail()
  email: string;

  @IsFQDN()
  site: string;

  @IsDate()
  createDate: Date;
}
  • 위 예시는 class-validator에서 발췌하였습니다.
validate(post).then(errors => {
  // errors is an array of validation errors
  if (errors.length > 0) {
    console.log('validation failed. errors: ', errors);
  } else {
    console.log('validation succeed');
  }
});
  • 그 후 validate를 호출하게 되면, 유효성 검사에 통과되지 않은 속성들에 대해서는 에러를 반환하게 되거나 별도의 예외처리를 하도록 콜백함수에 작성해주면 됩니다.
  • 저 같은 경우는 에러를 반환하게되면 412에러를 반환해주면서, 어떤 속성에서 문제가 발생되었는지 담아서 반환해주고 있습니다.

공통화

create = asnyc (req: Requset, res: Response) => {
   const dto = classToInstance(User, req.body);
   validate(user);
   
   const createdUser = await UserService.create(dto);
}
  • 위와 같은 컨트롤러의 메소드가 있다고 가정하겠습니다.
  • 서비스 레이어로 넘어간 데이터는 비즈니스로직 상에서 검증해야할 유효성만을 실행하고,
    전제조건이 잘못된 데이터는 컨트롤러단에서 제어해줍니다.

  • 즉, 컨트롤러단에서 위에 대한 코드는 어떤 메소드에서던지 일반적으로 무조건 반복해서 호출되게 됩니다.
  • 그렇다면, classToInstance와 validate를 모든 메소드에서 반복해서 호출해주는 것은 실수의 여지도 있고 상당히 비효율적입니다.

  • 그래서 Dto를 정의할 때는 공통적으로 해당 함수들을 호출해주는 공통 클래스를 상속받도록 정의하여 호출하면 유효성검사가 수행된 인스턴스화된 객체를 넘겨받도록 사용할 수 있습니다.
Class CommonDto {
	static getValidatedInstance<T extends CommonDto>(this: ClassConstructon<T>, req: Request): T {
		const dto = plainToInstance(this, req.build, 설정할 옵션);
		validateSync(dto);
		return dto;
   }
}
  • 처음 위와 같은 형식으로 작성했을 때의 문제점은 dto의 어떤 property의 유효성에서 문제가 발생한지 알 수 없었고, 어떤 내용의 에러가 발생했는지 파악하기 힘들었습니다.

  • 그래서 실무에선 class-validator의 validateSync를 바로 호출하는 것이 아닌,
    반복문을 순회하면서 어디서 문제가 발생했는지 담아서 한꺼번에 반환하는 별도 커스템 메소드를 두어 사용하고 있습니다.
  • 이 부분은 프론트와 협의 혹은 사내 에러 컨벤션의 형태에 따라 적합하게 처리하면 될 것 같습니다.

Whitelisting

import { validate } from 'class-validator';
// ...
validate(post, { whitelist: true });
Even if your object is an instance of a validation class it can contain additional properties that are not defined.
If you do not want to have such properties on your object, pass special flag to validate method
  • 유효성 검사가 통과된 인스턴스화된 변수일지여도, 정의하지 않은 추가적인 프로퍼티가 추가되어 있을 수 있습니다.
  • 만약 이러한 상황을 방지하려면 validate 메소드에 whitelist 옵션을 주는 것으로 방지할 수 있습니다.

  • 위 옵션만 추가할 경우 전달받을 수는 있지만, 전달받는 것만으로 에러가 발생하진 않습니다.
  • 실제 존재하지 않는 속성에 대해서 접근하는 경우에 에러가 발생하게 됩니다.

  • 만약 whitelist에 포함되지 않은 속성이 전달되는 경우에 대해서도 에러를 발생시키려면 아래와 같은 옵션을 추가할 수 있습니다.
import { validate } from 'class-validator';
// ...
validate(post, { whitelist: true, forbidNonWhitelisted: true });

 

E.O.D

  • 최근들어 작성한 포스팅들에서는 사용하고 있던 라이브러리 혹은 언어들의 기본사양에 대해서 다시 한 번 복기하고 몰랐던 부분에 대해서 정리할 수 있었습니다.

  • 해당 포스팅 본문의 공통화에 잠깐 작성한 부분과 같은 경우는 제가 진행하고 있는 프로젝트에서는 잘 사용중에 있지만, 다른 프로젝트에서는 사용되고 있지 않아서 반복적인 코드들이 많이 남아있는 경우도 있었습니다.

  • 바쁜 일정속에 동일한 문제에 대해 논의하기까지는 부담스럽지 않나 생각했었지만, 동료분이 원인을 물어봤을 때도 한 번에 예상가는 문제점에 대해서 떠올릴 수 있으려면 이런 일반적인 부분들은 어느 정도 프레임워크화? 공통화가 되어 있어야 한다고 느꼈습니다.