낙서장이자 오답 노트이자 컨닝 페이퍼

패스트캠퍼스 환급챌린지 8일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기 본문

NestJS

패스트캠퍼스 환급챌린지 8일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기

NangIn 2025. 4. 8. 20:08

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

강의 내용 정리   

 

Class Transformer 

 

클래스 트랜스포머란?

  • class-validator와 함께 자주 사용하는 형식 변환(Transformation) 라이브러리
  • 주로 요청 데이터를 클래스 인스턴스로 바꾸고, 필요 시 값을 자동 가공(transform)
  • 예: "123"(문자열) → 123(숫자), "EMAIL@EXAMPLE.COM" → "email@example.com" 등

 

주요 기능 

기능 설명
값 변환 (@Transform) 특정 필드의 값을 가공/수정할 수 있음
중첩 객체 변환 (@Type) 클래스 안의 다른 클래스도 자동으로 인스턴스화
출력 제어 (@Exclude, @Expose) @Exclude() → 해당 필드를 출력 결과에서 제외
@Expose() → @Expose()로 전체를 감춘 후, 노출할 필드만 선택
양방향 직렬화 지원 plainToInstance, instanceToPlain로 일반 객체 ↔ 클래스 변환 가능
커스텀 처리 유연함 복잡한 변환도 사용자 정의 트랜스포머로 처리 가능

 

사용 예시

  • 간단한 값 변환
  • import { Transform } from 'class-transformer';
    
    class UserDto {
      @Transform(({ value }) => value.toLowerCase()) // 이메일 값을 항상 소문자로 변환
      email: string;
    }
    
  • 중첩 클래스 변환
    • plain 객체의 중첩 구조를 실제 클래스로 자동 변환하고 싶을 때 사용
    • import { Type } from 'class-transformer';
      
      class Address {
        city: string;
        country: string;
      }
      
      class User {
        name: string;
      
        @Type(() => Address) // 중첩된 객체를 Address 인스턴스로 변환
        address: Address;
      }
      
    • 변환: plain → class instance
    • const plainObject = {
        name: 'Alice',
        address: {
          city: 'New York',
          country: 'USA',
        },
      };
      const userInstance = plainToInstance(User, plainObject); // plainToInstance(User, plainObj) 호출 시 address는 자동으로 Address 인스턴스로 변환됨
      
      console.log(userInstance instanceof User); // true
      console.log(userInstance.address instanceof Address); // true
      console.log(userInstance);
      /*
      User {
        name: 'Alice',
        address: Address { city: 'New York', country: 'USA' }
      }
      */
      
  • 출력 제외/선택
  • import { Exclude, Expose } from 'class-transformer';
    
    @Exclude()
    class User {
      @Expose()
      name: string;
    
      password: string; // 노출되지 않음
    }
    
     

 

주요 함수

함수 역할
plainToInstance(Class, obj) 일반 JS 객체 → 클래스 인스턴스
instanceToPlain(instance) 클래스 인스턴스 → 일반 객체 (JSON 응답 직렬화 시 사용)
  • NestJS에서는 대부분 ValidationPipe가 이 과정 자동 처리 → 데코레이터만 잘 붙이면 끝!

 

클래스 트랜스포머 vs 클래스 밸리데이터  

항목 class-validator class-transformer
목적 유효성 검사 (Validation) 값 변환 (Transformation)
대표 데코레이터 @IsString(), @IsEmail() 등 @Transform(), @Type() 등
역할 "이 값, 맞는 값이야?" "이 값, 이렇게 바꿔줄게!"
실행 시점 요청 받았을 때 검사 값을 받자마자 변환

 

Expose와 Exclude 적용해보기

 

클래스 트랜스포머는 클래스 인스턴스에만 적용됨

  • 인터페이스에는 적용되지 않으므로, 반드시 클래스 형태의 엔티티를 사용해야 함.
  • 예: interface 대신 movie.entity.ts에 Movie 클래스를 만들어 사용.

 

객체가 아닌 new Movie() 인스턴스를 사용해야 함

  • 그냥 { title: '해리포터' } 이런 객체를 넣으면 트랜스포머가 작동하지 않음.
  • new Movie()를 통해 명시적으로 인스턴스 생성해야 트랜스포머가 작동함.
  •   private movies: Movie[] = [];
    
      constructor() {
        const movie1 = new Movie();
    
        movie1.id = 1;
        movie1.title = '해리포터';
        movie1.genre = 'fantasy';
    
        const movie2 = new Movie();
    
        movie2.id = 2;
        movie2.title = '반지의 제왕';
        movie2.genre = 'action';
    
        this.movies.push(movie1, movie2);
      }
    

 

@Exclude() & @Expose() 사용법

  • @Exclude(): 해당 프로퍼티를 응답 결과에서 제외
    • 예: @Exclude() genre: string; → genre는 응답에서 제외됨
    • 전체 클래스에 @Exclude() 설정하면 모든 필드가 숨겨지고, @Expose()로 선택 노출 가능
  • @Expose(): 제외된 필드 중에서 응답에 포함시킬 필드만 지정
    • 보안이 중요한 경우 전체에 @Exclude() 적용 후, 필요한 필드만 @Expose()로 노출
    • @Exclude()
      export class Movie {
        @Expose()
        title: string;
      
        @Expose()
        id: number;
      
        genre: string; // 숨겨짐
      }
      

 

클래스 트랜스포머가 동작하게 하려면?

  • NestJS에서는 명시적으로 설정 필요:
  • @UseInterceptors(ClassSerializerInterceptor)
    
  • 해당 데코레이터를 컨트롤러에 추가해야 @Expose, @Exclude가 작동함

 

추가 기능: Getter도 노출 가능

  • 일반 프로퍼티가 아닌 getter 함수도 @Expose()로 응답에 포함 가능 (원래 응답에 getter는 포함 안됐음)
  • 값에 접근할 때 getter 유용
  • @Expose()
    get description() {
      return `id: ${this.id} title: ${this.title}`;
    }
    

 

Custom Transformer 사용해보기

 

목적

  • 클래스 프로퍼티의 값을 원하는 형태로 가공하여 반환하고 싶을 때 사용
  • 예: 모든 문자열을 대문자로, 특정 문자열로 치환 등

 

기본 형식

import { Transform } from 'class-transformer';

class Movie {
  @Transform(({ value }) => value.toUpperCase()) // 항상 대문자로 변환
  genre: string;
}
  • @Transform(({ value }) => ...) → 해당 프로퍼티의 원래 값에 가공 로직을 적용해서 반환

 

예시 1: 특정 문자열로 강제 고정

@Transform(({ value }) => '코드팩토리')
genre: string;
  • → 어떤 값을 넣어도 genre는 항상 "코드팩토리"로 처리됨

 

예시 2: 대문자 변환

@Transform(({ value }) => value.toString().toUpperCase())
genre: string;
  • → "fantasy" → "FANTASY"
  • → "액션" → "액션" (영문자만 대문자화됨)

 

응용 아이디어

  • 전화번호 마스킹: "01012345678" → "010****5678"
  • 금액 쉼표 삽입: 1234567 → "1,234,567"
  • 날짜 포맷 변환: "2024-07-21T10:00:00Z" → "2024년 7월 21일"

 

마무리 정리

  • Transform()은 단순 출력 가공뿐 아니라 저장 전 값 변환, 응답 가공, 로깅 처리에도 활용 가능
  • 불필요한 데이터 가공 로직을 DTO에서 처리할 수 있어 서비스 로직 분리 + 테스트 용이성 상승
  • 실무에서도 입력 정제, 출력 포맷팅, 시큐리티 처리 등 다양하게 쓰임

 

Joi 이론

 

Joi란?

  • JavaScript/TypeScript에서 사용하는 스키마 기반의 객체 검증 라이브러리
  • class-validator와는 다르게 데코레이터가 아닌, 명시적인 스키마 정의를 통해 유효성 검사를 수행
  • NestJS에서도 사용 가능하지만, 보통은 환경 변수 검증용으로만 주로 사용됨

 

Joi의 주요 특징 

기능 설명
스키마 기반 구조 Joi.object({})로 구조를 명시
타입 세이프 타입에 따라 자동 검사 (Joi.string(), Joi.number() 등)
에러 메시지 설정 .message()로 커스텀 메시지 지정 가능
커스텀 검증 로직 .extend()로 직접 Validator 확장 가능
NestJS에서 사용처 환경 변수 검증용으로 주로 사용됨 (ConfigModule)

 

사용 예시

import * as Joi from 'joi';

const schema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email().required(),
  age: Joi.number().optional(),
});

const data = {
  name: 'Alice',
  email: 'alice@example.com',
};

const { error, value } = schema.validate(data);

if (error) {
  console.log('유효성 실패:', error.message);
} else {
  console.log('유효한 데이터:', value);
}

 

커스텀 메시지

Joi.string().required().messages({
  'string.empty': '이름은 비워둘 수 없습니다.',
});

 

커스텀 확장 예시

const extendedJoi = Joi.extend((joi) => ({
  type: 'string',
  base: joi.string(),
  messages: {
    'string.capitalized': '{{#label}}는 첫 글자가 대문자여야 합니다.',
  },
  rules: {
    capitalized: {
      validate(value, helpers) {
        if (value[0] !== value[0].toUpperCase()) {
          return helpers.error('string.capitalized');
        }
        return value;
      },
    },
  },
}));

extendedJoi.string().capitalized().validate('hello'); // ❌

 

NestJS에서의 실전 활용

  • NestJS에서는 대부분 class-validator를 사용하지만,
  • .env 파일의 환경 변수 검증에는 Joi를 사용함
  • ConfigModule.forRoot({ validationSchema: Joi.object({...}) }) 형태로 사용

 

핵심 요약

  • Joi는 객체 단위로 유효성을 정의하는 도구
  • 실무에서는 주로 환경 변수 검증에 사용되고
  • 일반적인 API 요청 검증은 class-validator가 훨씬 더 직관적이고 효율적임
  • 단, 스키마 기반 검증의 유연성이 필요할 땐 Joi가 유리한 선택이 될 수도 있음

                                  

학습 후기                                                                                                                                       

이번 강의를 통해 class-transformer가 단순한 값 변환 도구를 넘어, 응답 데이터의 형태를 명확히 통제할 수 있는 강력한 기능임을 알게 되었습니다. 특히 @Exclude()와 @Expose()를 통해 DTO의 출력 구조를 세밀하게 조정할 수 있다는 점이 인상 깊었고, 보안이나 응답 최적화 측면에서도 실무에서 꼭 활용해야겠다는 생각이 들었습니다. 단순히 값을 가공하는 @Transform() 기능도, 서비스 로직에서 처리하던 반복적인 작업을 DTO에서 깔끔하게 처리할 수 있어 생산성과 유지보수성이 높아진다는 장점이 느껴졌습니다.

 

중첩 객체를 자동으로 클래스 인스턴스로 변환해주는 @Type()의 사용도 매우 유용했습니다. 특히 복잡한 관계형 구조를 다루는 데 있어 이 기능은 실무에서 반복해서 마주치는 요구사항을 효과적으로 해결할 수 있는 방법이라는 생각이 들었습니다. 또한 NestJS에서 ClassSerializerInterceptor를 통해 해당 기능들을 컨트롤러에 적용하는 방식도 명확히 이해하게 되어, 실무에서 직접 활용하는 데 무리가 없겠다는 자신감도 생겼습니다.

 

Joi는 이전에 환경 변수 검증에서만 접했었지만, 이번 학습을 통해 단순한 데이터 유효성 검사 이상의 역할을 한다는 걸 알게 되었습니다. 객체 스키마 기반 구조를 명시적으로 설정할 수 있어서 복잡한 환경 구성을 검증할 때 매우 유용했고, class-validator와의 차이점도 명확히 구분할 수 있어 어떤 상황에 어떤 도구를 써야 하는지도 자연스럽게 체득되었습니다.

 

학습 인증샷       

수강 인증 사진

                         

학습 인증샷

                       

공부 시작 시간

                                                                       

공부 종료 시간

https://abit.ly/lisbva

 

Abit.ly 다운받기

 

abit.ly