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

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

NestJS

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

NangIn 2025. 4. 7. 10:33

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

 

강의 내용 정리   

 

기본 Validator 정리

 

공식 문서 보는 법

 

필드 존재 여부 → 요청 바디에 해당 필드가 포함되어 있는지 여부 확인

@IsDefined() null, undefined가 허용되지 않음 → 필드가 반드시 존재해야 함 @IsDefined()
@IsOptional() 해당 필드는 있어도 되고 없어도 됨 → 존재할 경우에만 아래의 데코레이터 실행. 즉, @IsOptional()과 함께 쓰면 선택적으로 유효성 검사 가능 @IsOptional() + @IsString() 조합 사용 가능

 

값 유무 → 필드가 존재할 경우, 그 안의 값이 비어있지 않은지 확인

@IsEmpty() 값이 null, undefined, '' 중 하나여야 함 → 값이 있으면 에러 보통 "비어 있어야 하는 필드"를 명시할 때 사용
@IsNotEmpty() 값이 null, undefined, '' 중 하나면 에러 → 값이 꼭 있어야 함 필수 입력값인 경우 사용

 

값 비교 → 특정 값과 일치하거나 일치하지 않아야 하는지 확인

@Equals('값') 정확히 특정 값과 일치해야만 통과 @Equals('admin')
@NotEquals('값') 특정 값과 일치하면 안 됨 @NotEquals('guest')

 

목록 기반 유효성 (In/NotIn) → 특정 값 목록과 비교하여 조건 판단

@IsIn([...]) 값이 리스트 내에 포함되어 있어야 함 @IsIn(['action', 'fantasy'])
@IsNotIn([...]) 값이 리스트에 포함되어 있으면 안 됨 @IsNotIn(['banned', 'blocked'])

 

  • 모든 데코레이터는 @Message() 옵션으로 커스텀 에러 메시지 설정 가능
@IsNotEmpty({ message: '제목은 반드시 입력해야 합니다.' })

 

타입 Validator 정리

 

기본 타입 검증

@IsBoolean() true, false 만 허용 "true"(문자열), null 등은 ❌
@IsString() 문자열인지 검사 "text" ✅ / 123 ❌
@IsNumber() 숫자인지 검사 123 ✅ / "123" ❌
@IsInt() 정수인지 검사 1 ✅ / 1.01 ❌

 

특수 타입

@IsArray() 배열인지 검사 [1, 2, 3] ✅ / "abc" ❌
@IsEnum(ENUM) 특정 Enum 값인지 검사 @IsEnum(MovieGenre)
@IsDateString() ISO 형식의 날짜 문자열인지 검사 "2024-07-21T12:00:00Z" ✅

 

@IsEnum() vs @IsIn() 비교

@IsEnum(MyEnum) 여러 곳에서 재사용 가능, 유지보수에 좋음
@IsIn(['A', 'B']) 단발성 조건에 유리함, 코드 내에 값 직접 명시

 

날짜 포맷 (ISO 8601)

  • 예시:
  • 2024-07-21T12:00:00.000Z (년-월-일 T 시:분:초.밀리초 Z)
  • 구성 요소:
    • : 날짜 구분 (년-월-일)
    • T: 날짜와 시간 사이 구분자
    • :: 시간 구분 (시:분:초)
    • .: 밀리초
    • Z: UTC 시간 (없으면 현지 시간으로 간주)

 

  • 단순한 타입 체크를 넘어서, API가 예상된 형식의 값만 받도록 방어막 역할 수행
  • 실무에서 자주 등장하는 핵심 타입: enum, boolean, 날짜 포맷(ISO 형식)
  • 익숙해질수록: 디버깅 시간 감소 + API 신뢰도 상승
  • 타입 밸리데이션은 개발자의 실수를 줄이고 사용자에게 더 정확한 피드백 제공

 

숫자 Validator 정리

 

나눌 수 있는 수 검사

@IsDivisibleBy(n) 값이 n으로 정확히 나눠떨어지는지 확인 @IsDivisibleBy(5) → 10, 15 ✅ / 7 ❌

 

양수 / 음수 체크

@IsPositive() 0보다 큰 수만 허용 1 ✅ / 0, -1 ❌
@IsNegative() 0보다 작은 수만 허용 -5 ✅ / 0, 5 ❌

 

최소 / 최대 값 설정

@Min(n) 값이 n 이상이어야 통과 @Min(100) → 100, 200 ✅ / 99 ❌
@Max(n) 값이 n 이하여야 통과 @Max(100) → 100, 50 ✅ / 150 ❌
@Min(n) + @Max(m) 범위 제한 (n 이상, m 이하) @Min(50) @Max(100) → 75, 55 ✅ / 30, 150 ❌

 

  • 조합해서 사용할 수 있음 → 여러 제약 조건을 동시에 적용 가능
  • 예: 특정 필드는 반드시 양수이며, 100 이상 1000 이하이어야 할 때
@IsNumber()
@IsPositive()
@Min(100)
@Max(1000)
  • @IsDivisibleBy()는 할인율, 페이지 수, 묶음 단위 같은 입력에 유용
  • @Min() + @Max()는 범위 제한 필수 조건일 때 기본
  • @IsPositive() / @IsNegative()는 입력값 방향성 제한할 때 사용

 

문자 Validator 정리

 

포함 여부 관련

@Contains(text) 문자열 안에 text가 포함되어 있어야 함 @Contains('hello') → "say hello" ✅
@NotContains(text) 문자열 안에 text가 포함되면 안 됨 @NotContains('admin') → "user123" ✅

 

문자 조합 관련

@IsAlphanumeric() 영문 + 숫자만 허용 (a~z, A~Z, 0~9) "abc123" ✅ / "abc 123", "한글" ❌
@IsHexColor() HEX 색상 코드 형식 (# 없이 6자리 16진수) "ffffff", "abc123" ✅ / "zzz999" ❌
@IsCreditCard() 유효한 카드 번호 포맷인지 확인 (Luhn 알고리즘 기반) "5312-XXXX-XXXX-XXXX" ✅ / "1234" ❌
@IsUUID() UUID 포맷인지 검사 (v4 기본) "a45fcd12-1234-4bc2-a8f9-df012b66c833" ✅

 

길이 관련

@MaxLength(n) 문자열 길이가 최대 n 이하여야 함 @MaxLength(16) → "1234567890123456" ✅
@MinLength(n) 문자열 길이가 최소 n 이상이어야 함 @MinLength(4) → "asdf" ✅
  • 함께 사용 가능:
@MinLength(4)
@MaxLength(16)

 

위치 관련

@IsLatitude() 위도 값인지 확인 (-90 ~ 90) "37.7749" ✅
@IsLongitude() 경도 값인지 확인 (-180 ~ 180) "122.4194" ✅

 

실무 사용 예시

비밀번호 검증 @IsAlphanumeric(), @MinLength(8), @MaxLength(20)
색상 HEX 입력 @IsHexColor()
UUID 입력 필드 @IsUUID()
카드번호 입력 필드 @IsCreditCard()

 

  • @Contains()와 @IsAlphanumeric()은 사용자 입력 검증에 매우 유용
  • @IsUUID()와 @IsCreditCard()는 ID나 결제 정보 처리 시 필수
  • @IsHexColor()는 디자인/스타일 데이터 전달 시 유용
  • 문자열 길이 검증은 @MinLength() + @MaxLength() 세트로 사용하는 것이 일반적

 

Custom Validator

 

커스텀 Validator 구성

  1. 검증 로직을 가진 클래스
  2. 사용자 정의 데코레이터 함수

 

클래스 정의

import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationArguments,
} from 'class-validator';

@ValidatorConstraint()
export class PasswordValidator implements ValidatorConstraintInterface {
  validate(value: string, args: ValidationArguments): boolean {
    // 비밀번호 길이는 4-8
    return value.length >= 4 && value.length <= 8;
  }

  defaultMessage(args: ValidationArguments): string {
    return `입력된 비밀번호(${args.value})의 길이는 4~8자여야 합니다.`;
  }
}

 

데코레이터 함수 정의

import {
  registerDecorator,
  ValidationOptions,
  ValidationArguments,
} from 'class-validator';

export function IsPasswordValid(validationOptions?: ValidationOptions) {
  return function (object: Object, propertyName: string) {
    registerDecorator({
      target: object.constructor,
      propertyName,
      options: validationOptions,
      validator: PasswordValidator,
    });
  };
}

 

사용 예시 (DTO에서)

export class CreateUserDto {
  @IsPasswordValid({ message: '비밀번호는 4~8자 사이여야 해요!' })
  password: string;
}

 

커스텀 메시지 활용

  • defaultMessage() 내부에서 $value를 통해 입력값 출력 가능 → 사용자가 어떤 값을 보냈는지 에러 메시지에 포함할 수 있음
defaultMessage(args: ValidationArguments): string {
  return `입력된 값 $value는 유효하지 않습니다.`;
}

 

비동기 검증 (옵션)

  • @ValidatorConstraint({ async: true }) 사용
  • validate() 메서드를 Promise<boolean> 타입으로 정의
@ValidatorConstraint({ async: true })
export class UserExistsValidator implements ValidatorConstraintInterface {
  async validate(userId: string): Promise<boolean> {
    const user = await this.userService.findById(userId);
    return !!user;
  }
}

 

Custom Validator 장점 요약

복잡한 로직 처리 가능 길이 + 문자 조합 등 다양한 검증 조건
비동기 작업 가능 DB 조회, API 호출 등 외부 자원 기반 검사 가능
메시지 커스터마이징 입력값 포함한 메시지, 다국어 처리도 가능
재사용성 뛰어남 다른 DTO에서도 쉽게 재활용 가능

 

  • 로그인 / 회원가입 / 결제 등 사용자 입력이 중요한 기능에서 매우 유용
  • 복잡한 조건을 여러 데코레이터로 나열하지 말고 하나로 묶어서 깔끔하게 관리
  • 여러 곳에서 쓰이는 공통 유효성 검사는 커스텀으로 추출하면 유지보수성 향상

 

ValidationPipe 주요 옵션 알아보기

 

설정 위치 (NestJS 진입 파일)

// main.ts
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
    }),
  );
  await app.listen(3000);
}

 

옵션 설명

whitelist false DTO에 정의되지 않은 속성을 자동으로 제거
forbidNonWhitelisted false DTO에 없는 속성이 들어오면 에러 발생

 

whitelist

  • 기능: DTO에 정의되지 않은 필드 제거
  • true로 설정 시, DTO에 명시되지 않은 속성은 자동 무시
{
  "title": "테스트 영화",
  "genre": "액션",
  "description": "이건 DTO에 없음 ❌"
}
  • description 필드는 저장되지 않음

 

forbidNonWhitelisted

  • 기능: DTO에 없는 필드가 있으면 에러 발생
  • whitelist: true 상태에서 함께 사용하는 것이 일반적
{
  "statusCode": 400,
  "message": ["property description should not exist"],
  "error": "Bad Request"
}
  • 실제 시스템에서 데이터 오염을 방지하는 데 매우 유용

 

실무 사용 예시

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
  }),
);

 

요약 포인트

  • whitelist = 안전망 → 정의되지 않은 필드를 제거해 불필요한 데이터 유입 방지
  • forbidNonWhitelisted = 방어벽 → DTO 외 값이 들어오면 에러로 응답해 API 신뢰도 확보
  • NestJS에서 ValidationPipe는 class-validator와 함께 사용할 때 강력한 역할을 함

                                                                                                               

학습 후기                                                                                                                                       

이번에 class-validator와 NestJS의 ValidationPipe에 대해 학습하면서, 단순한 데코레이터의 나열이 아닌 입력 데이터의 신뢰성을 확보하기 위한 체계적인 전략이 얼마나 중요한지를 실감할 수 있었습니다. 강의를 통해 각 데코레이터가 어떤 역할을 수행하는지 구체적으로 이해하게 되었고, 특히 @IsOptional()과 @IsDefined()의 차이처럼 존재 여부 자체를 검증하는 방식이 API 설계에서 얼마나 유용한지를 깨달았습니다.

 

단순히 문법을 외우는 것이 아니라, 각 데코레이터가 어떤 상황에서 쓰이는지에 대해 실제 사례 중심으로 학습할 수 있어서 더욱 인상 깊었습니다. 예를 들어, @IsEnum()과 @IsIn()의 차이를 비교하면서 재사용성과 유지보수성까지 고려한 선택이 가능하다는 점이 실무적인 시각을 넓히는 데 많은 도움이 되었습니다. 실수로 잘못된 타입이 들어오는 상황을 방지하고, 사용자에게 명확한 에러 메시지를 제공할 수 있다는 점에서 큰 장점을 느꼈습니다.

 

또한, ValidationPipe의 whitelist, forbidNonWhitelisted 옵션을 통해 DTO의 안전성을 한층 강화할 수 있다는 점도 매우 인상 깊었습니다. 의도하지 않은 데이터가 유입되었을 때 자동으로 걸러내거나 에러를 발생시킴으로써, 백엔드 로직을 보호할 수 있다는 점에서 이 옵션은 필수적으로 고려해야 한다는 인사이트를 얻었습니다. 특히 실제 프로젝트에서 다양한 요청 바디가 들어올 경우를 고려하면, 이 두 옵션의 조합은 데이터 오염을 방지하는 데 필수적인 도구라고 생각됩니다.

 

커스텀 Validator 구현 파트에서는 단순한 데코레이터 조합으로는 표현하기 어려운 복잡한 검증 로직을 캡슐화할 수 있다는 점에서 큰 매력을 느꼈습니다. 특히, ValidatorConstraint와 registerDecorator를 조합하여 비즈니스 로직에 맞는 검증 조건을 분리하고 재사용 가능한 유효성 검사 유닛으로 만들 수 있다는 점은, 코드의 가독성과 유지보수성을 모두 확보할 수 있는 매우 유용한 방법이라는 생각이 들었습니다.

 

이번 학습을 통해 단순히 "입력값을 검증한다"는 수준을 넘어, "어떻게 하면 사용자에게 더 명확하고 안정적인 API를 제공할 수 있을까?"라는 관점으로 접근하는 태도를 기를 수 있었습니다. 앞으로 실제 개발을 진행하면서 class-validator를 적용할 때는, 단순한 데코레이터 조합을 넘어서 도메인 요구사항에 맞는 의미 있는 유효성 검증 구조를 설계하는 것이 중요하다는 점을 명심하게 될 것 같습니다.

 

학습 인증샷       

수강 인증 사진

                         

학습 인증샷

                       

공부 시작 시간

                                                                       

공부 종료 시간

https://abit.ly/lisbva

 

Abit.ly 다운받기

 

abit.ly