낙서장이자 오답 노트이자 컨닝 페이퍼
패스트캠퍼스 환급챌린지 7일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기 본문
패스트캠퍼스 환급챌린지 7일차 : 코드팩토리의 백엔드 아카데미 : 한 번에 끝내는 NestJS 패키지 - 기초부터 MSA까지 강의 후기
NangIn 2025. 4. 7. 10:33본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
강의 내용 정리
기본 Validator 정리
공식 문서 보는 법
- class-validator GitHub 문서 → Validation Decorators 섹션 참고
필드 존재 여부 → 요청 바디에 해당 필드가 포함되어 있는지 여부 확인
| @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 구성
- 검증 로직을 가진 클래스
- 사용자 정의 데코레이터 함수
클래스 정의
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