NestJS Logo

유효성 검증

웹 애플리케이션으로 전송되는 모든 데이터의 정확성을 검증하는 것은 좋은 습관입니다. Nest는 들어오는 요청을 자동으로 검증하기 위해 즉시 사용할 수 있는 여러 파이프를 제공합니다:

  • ValidationPipe
  • ParseIntPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe

ValidationPipe는 강력한 class-validator 패키지와 선언적 유효성 검사 데코레이터를 활용합니다. ValidationPipe는 들어오는 모든 클라이언트 페이로드에 대한 유효성 검사 규칙을 적용하는 편리한 접근 방식을 제공하며, 특정 규칙은 각 모듈의 로컬 클래스/DTO 선언에 간단한 애너테이션으로 선언됩니다.

개요#

파이프 챕터에서, 우리는 간단한 파이프를 구축하고 컨트롤러, 메소드 또는 전역 앱에 바인딩하여 프로세스가 어떻게 작동하는지 시연했습니다. 이 챕터의 주제를 더 잘 이해하기 위해 해당 챕터를 다시 살펴보세요. 여기서는 ValidationPipe의 다양한 실제 사용 사례에 초점을 맞추고, 일부 고급 커스터마이징 기능을 사용하는 방법을 보여드리겠습니다.

내장 ValidationPipe 사용하기#

사용을 시작하려면 먼저 필요한 의존성을 설치해야 합니다.


$ npm i --save class-validator class-transformer
힌트ValidationPipe@nestjs/common 패키지에서 익스포트됩니다.

이 파이프는 class-validatorclass-transformer 라이브러리를 사용하므로 다양한 옵션을 사용할 수 있습니다. 이러한 설정은 파이프에 전달되는 설정 객체를 통해 구성합니다. 다음은 내장 옵션입니다:


export interface ValidationPipeOptions extends ValidatorOptions {
  transform?: boolean;
  disableErrorMessages?: boolean;
  exceptionFactory?: (errors: ValidationError[]) => any;
}

이 외에도 모든 class-validator 옵션 (ValidatorOptions 인터페이스에서 상속됨)을 사용할 수 있습니다:

옵션타입설명
enableDebugMessagesbooleantrue로 설정하면, 검증기가 뭔가 잘못되었을 때 추가 경고 메시지를 콘솔에 출력합니다.
skipUndefinedPropertiesbooleantrue로 설정하면, 검증 대상 객체에서 undefined인 모든 속성의 유효성 검사를 건너뜁니다.
skipNullPropertiesbooleantrue로 설정하면, 검증 대상 객체에서 null인 모든 속성의 유효성 검사를 건너뜁니다.
skipMissingPropertiesbooleantrue로 설정하면, 검증 대상 객체에서 null 또는 undefined인 모든 속성의 유효성 검사를 건너뜁니다.
whitelistbooleantrue로 설정하면, 검증된 (반환된) 객체에서 유효성 검사 데코레이터를 사용하지 않은 속성을 모두 제거합니다.
forbidNonWhitelistedbooleantrue로 설정하면, 화이트리스트에 없는 속성을 제거하는 대신 예외를 발생시킵니다.
forbidUnknownValuesbooleantrue로 설정하면, 알 수 없는 객체를 검증하려는 시도는 즉시 실패합니다.
disableErrorMessagesbooleantrue로 설정하면, 유효성 검사 오류가 클라이언트에게 반환되지 않습니다.
errorHttpStatusCodenumber이 설정을 사용하면 오류 발생 시 어떤 예외 타입이 사용될지 지정할 수 있습니다. 기본적으로 BadRequestException을 던집니다.
exceptionFactoryFunction유효성 검사 오류 배열을 받아 던질 예외 객체를 반환합니다.
groupsstring[]객체 유효성 검사 중에 사용할 그룹입니다.
alwaysboolean데코레이터의 always 옵션에 대한 기본값을 설정합니다. 기본값은 데코레이터 옵션에서 재정의될 수 있습니다.
strictGroupsbooleangroups가 주어지지 않았거나 비어 있는 경우, 하나 이상의 그룹을 가진 데코레이터를 무시합니다.
dismissDefaultMessagesbooleantrue로 설정하면, 유효성 검사는 기본 메시지를 사용하지 않습니다. 오류 메시지는 명시적으로 설정되지 않으면 항상 undefined입니다.
validationError.targetbooleanValidationError에 대상이 노출되어야 하는지 여부를 나타냅니다.
validationError.valuebooleanValidationError에 검증된 값이 노출되어야 하는지 여부를 나타냅니다.
stopAtFirstErrorbooleantrue로 설정하면, 주어진 속성의 유효성 검사는 첫 번째 오류를 만난 후 중단됩니다. 기본값은 false입니다.
참고class-validator 패키지에 대한 자세한 정보는 리포지토리에서 찾을 수 있습니다.

자동 유효성 검사#

ValidationPipe를 애플리케이션 레벨에 바인딩하여 모든 엔드포인트가 잘못된 데이터를 받지 않도록 보호하는 것부터 시작하겠습니다.


async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

파이프를 테스트하기 위해 간단한 엔드포인트를 만들어 보겠습니다.


@Post()
create(@Body() createUserDto: CreateUserDto) {
  return 'This action adds a new user';
}
힌트 TypeScript는 제네릭 또는 인터페이스에 대한 메타데이터를 저장하지 않으므로 DTO에서 이러한 타입을 사용하는 경우, ValidationPipe가 들어오는 데이터를 제대로 검증하지 못할 수 있습니다. 따라서 DTO에서는 구체적인 클래스를 사용하는 것이 좋습니다.
힌트 DTO를 임포트할 때, 런타임에 지워지는 타입 전용 임포트를 사용할 수 없습니다. 즉, import type { CreateUserDto } 대신 import { CreateUserDto }와 같이 임포트해야 합니다.

이제 CreateUserDto에 몇 가지 유효성 검사 규칙을 추가할 수 있습니다. 이는 class-validator 패키지에서 제공하는 데코레이터를 사용하여 수행하며, 여기에 자세히 설명되어 있습니다. 이러한 방식으로 CreateUserDto를 사용하는 모든 라우트는 자동으로 이 유효성 검사 규칙을 적용합니다.


import { IsEmail, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsNotEmpty()
  password: string;
}

이 규칙이 적용되면, 요청 본문의 email 속성에 잘못된 값이 포함된 요청이 엔드포인트에 도달하면, 애플리케이션은 다음과 같은 응답 본문과 함께 자동으로 400 Bad Request 코드로 응답합니다:


{
  "statusCode": 400,
  "error": "Bad Request",
  "message": ["email must be an email"]
}

ValidationPipe는 요청 본문 외에도 다른 요청 객체 속성과 함께 사용할 수 있습니다. 엔드포인트 경로에 :id를 허용하고 싶다고 상상해 보세요. 이 요청 파라미터에 숫자만 허용되도록 보장하기 위해 다음 구성을 사용할 수 있습니다:


@Get(':id')
findOne(@Param() params: FindOneParams) {
  return 'This action returns a user';
}

FindOneParams는 DTO와 마찬가지로 class-validator를 사용하여 유효성 검사 규칙을 정의하는 단순한 클래스입니다. 다음과 같습니다:


import { IsNumberString } from 'class-validator';

export class FindOneParams {
  @IsNumberString()
  id: string;
}

상세 오류 비활성화#

오류 메시지는 요청에서 무엇이 잘못되었는지 설명하는 데 도움이 될 수 있습니다. 그러나 일부 프로덕션 환경에서는 상세 오류를 비활성화하는 것을 선호합니다. ValidationPipe에 옵션 객체를 전달하여 이를 수행할 수 있습니다:


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

결과적으로 응답 본문에 상세 오류 메시지가 표시되지 않습니다.

속성 제거 (Stripping properties)#

ValidationPipe는 메소드 핸들러가 받아서는 안 되는 속성을 필터링할 수도 있습니다. 이 경우 허용 가능한 속성을 화이트리스트로 지정할 수 있으며, 화이트리스트에 포함되지 않은 모든 속성은 결과 객체에서 자동으로 제거됩니다. 예를 들어, 핸들러가 emailpassword 속성을 예상하지만 요청에 age 속성도 포함된 경우, 이 속성은 결과 DTO에서 자동으로 제거될 수 있습니다. 이러한 동작을 활성화하려면 whitelisttrue로 설정합니다.


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

true로 설정하면 유효성 검사 클래스에 데코레이터가 없는 속성(즉, 화이트리스트에 없는 속성)이 자동으로 제거됩니다.

또는 화이트리스트에 없는 속성이 있을 때 요청 처리를 중단하고 사용자에게 오류 응답을 반환할 수 있습니다. 이를 활성화하려면 whitelisttrue로 설정하는 것과 함께 forbidNonWhitelisted 옵션 속성을 true로 설정합니다.

페이로드 객체 변환#

네트워크를 통해 들어오는 페이로드는 일반 JavaScript 객체입니다. ValidationPipe는 페이로드를 DTO 클래스에 따라 타입이 지정된 객체로 자동으로 변환할 수 있습니다. 자동 변환을 활성화하려면 transformtrue로 설정합니다. 이는 메소드 레벨에서 수행할 수 있습니다:

cats.controller.ts
JS TS

@Post()
@UsePipes(new ValidationPipe({ transform: true }))
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

이 동작을 전역적으로 활성화하려면 전역 파이프에 옵션을 설정합니다:


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

자동 변환 옵션이 활성화되면 ValidationPipe는 원시 타입 변환도 수행합니다. 다음 예제에서 findOne() 메소드는 추출된 id 경로 매개변수를 나타내는 하나의 인수를 받습니다:


@Get(':id')
findOne(@Param('id') id: number) {
  console.log(typeof id === 'number'); // true
  return 'This action returns a user';
}

기본적으로 모든 경로 매개변수 및 쿼리 매개변수는 네트워크를 통해 string으로 전달됩니다. 위 예제에서는 id 타입을 number로 지정했습니다 (메소드 시그니처에서). 따라서 ValidationPipe는 문자열 식별자를 숫자로 자동 변환하려고 시도합니다.

명시적 변환#

위 섹션에서는 ValidationPipe가 예상되는 타입에 따라 쿼리 및 경로 매개변수를 암시적으로 변환하는 방법을 보여주었습니다. 그러나 이 기능은 자동 변환이 활성화되어 있어야 합니다.

대안으로 (자동 변환이 비활성화된 경우), ParseIntPipe 또는 ParseBoolPipe를 사용하여 값을 명시적으로 캐스트할 수 있습니다 (ParseStringPipe는 앞서 언급했듯이 모든 경로 매개변수 및 쿼리 매개변수가 기본적으로 네트워크를 통해 string으로 전달되기 때문에 필요하지 않습니다).


@Get(':id')
findOne(
  @Param('id', ParseIntPipe) id: number,
  @Query('sort', ParseBoolPipe) sort: boolean,
) {
  console.log(typeof id === 'number'); // true
  console.log(typeof sort === 'boolean'); // true
  return 'This action returns a user';
}
힌트ParseIntPipeParseBoolPipe@nestjs/common 패키지에서 익스포트됩니다.

매핑된 타입 (Mapped types)#

CRUD (생성/읽기/업데이트/삭제)와 같은 기능을 구축할 때 기본 엔티티 타입의 변형을 구성하는 것이 유용할 때가 많습니다. Nest는 이 작업을 더 편리하게 만들기 위해 타입 변환을 수행하는 여러 유틸리티 함수를 제공합니다.

경고 애플리케이션이 @nestjs/swagger 패키지를 사용하는 경우, 매핑된 타입에 대한 자세한 정보는 이 챕터를 참조하십시오. 마찬가지로 @nestjs/graphql 패키지를 사용하는 경우 이 챕터를 참조하십시오. 두 패키지 모두 타입에 크게 의존하므로 다른 임포트가 필요합니다. 따라서 @nestjs/mapped-types (앱 타입에 따라 적절한 @nestjs/swagger 또는 @nestjs/graphql 대신)을 사용한 경우 다양한 문서화되지 않은 부작용에 직면할 수 있습니다.

입력 유효성 검사 타입 (DTO라고도 함)을 구축할 때 동일한 타입에 대한 생성업데이트 변형을 구축하는 것이 유용할 때가 많습니다. 예를 들어, 생성 변형은 모든 필드를 요구할 수 있지만, 업데이트 변형은 모든 필드를 선택 사항으로 만들 수 있습니다.

Nest는 이 작업을 더 쉽게 하고 상용구 코드를 최소화하기 위해 PartialType() 유틸리티 함수를 제공합니다.

PartialType() 함수는 입력 타입의 모든 속성이 선택 사항으로 설정된 타입 (클래스)을 반환합니다. 예를 들어, 다음과 같은 생성 타입이 있다고 가정합니다:


export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

기본적으로 이 필드는 모두 필수입니다. 동일한 필드를 가지지만 각 필드가 선택 사항인 타입을 생성하려면 PartialType()에 클래스 참조 (CreateCatDto)를 인수로 전달하여 사용합니다:


export class UpdateCatDto extends PartialType(CreateCatDto) {}
힌트PartialType() 함수는 @nestjs/mapped-types 패키지에서 임포트됩니다.

PickType() 함수는 입력 타입에서 속성 집합을 선택하여 새 타입 (클래스)을 구성합니다. 예를 들어, 다음과 같은 타입에서 시작한다고 가정합니다:


export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

PickType() 유틸리티 함수를 사용하여 이 클래스에서 속성 집합을 선택할 수 있습니다:


export class UpdateCatAgeDto extends PickType(CreateCatDto, ['age'] as const) {}
힌트PickType() 함수는 @nestjs/mapped-types 패키지에서 임포트됩니다.

OmitType() 함수는 입력 타입에서 모든 속성을 선택한 다음 특정 키 집합을 제거하여 타입을 구성합니다. 예를 들어, 다음과 같은 타입에서 시작한다고 가정합니다:


export class CreateCatDto {
  name: string;
  age: number;
  breed: string;
}

다음과 같이 name제외한 모든 속성을 가진 파생된 타입을 생성할 수 있습니다. 이 구성에서 OmitType의 두 번째 인수는 속성 이름의 배열입니다.


export class UpdateCatDto extends OmitType(CreateCatDto, ['name'] as const) {}
힌트OmitType() 함수는 @nestjs/mapped-types 패키지에서 임포트됩니다.

IntersectionType() 함수는 두 타입을 하나로 결합하여 새로운 타입 (클래스)을 만듭니다. 예를 들어, 다음과 같은 두 타입에서 시작한다고 가정합니다:


export class CreateCatDto {
  name: string;
  breed: string;
}

export class AdditionalCatInfo {
  color: string;
}

두 타입의 모든 속성을 결합한 새 타입을 생성할 수 있습니다.


export class UpdateCatDto extends IntersectionType(
  CreateCatDto,
  AdditionalCatInfo,
) {}
힌트IntersectionType() 함수는 @nestjs/mapped-types 패키지에서 임포트됩니다.

타입 매핑 유틸리티 함수는 조합 가능합니다. 예를 들어, 다음은 CreateCatDto 타입의 name을 제외한 모든 속성을 가지며, 이 속성들이 선택 사항으로 설정된 타입 (클래스)을 생성합니다:


export class UpdateCatDto extends PartialType(
  OmitType(CreateCatDto, ['name'] as const),
) {}

배열 파싱 및 유효성 검사#

TypeScript는 제네릭 또는 인터페이스에 대한 메타데이터를 저장하지 않으므로 DTO에서 이러한 타입을 사용하는 경우, ValidationPipe가 들어오는 데이터를 제대로 검증하지 못할 수 있습니다. 예를 들어, 다음 코드에서 createUserDtos는 올바르게 검증되지 않습니다:


@Post()
createBulk(@Body() createUserDtos: CreateUserDto[]) {
  return 'This action adds new users';
}

배열의 유효성을 검사하려면 배열을 래핑하는 속성을 포함하는 전용 클래스를 생성하거나 ParseArrayPipe를 사용하십시오.


@Post()
createBulk(
  @Body(new ParseArrayPipe({ items: CreateUserDto }))
  createUserDtos: CreateUserDto[],
) {
  return 'This action adds new users';
}

또한 ParseArrayPipe는 쿼리 매개변수를 파싱할 때 유용하게 사용될 수 있습니다. 쿼리 매개변수로 전달된 식별자를 기반으로 사용자를 반환하는 findByIds() 메소드를 고려해 보겠습니다.


@Get()
findByIds(
  @Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
  ids: number[],
) {
  return 'This action returns users by ids';
}

이 구성은 다음과 같은 HTTP GET 요청에서 들어오는 쿼리 매개변수를 검증합니다:


GET /?ids=1,2,3

WebSockets 및 마이크로서비스#

이 챕터에서는 HTTP 스타일 애플리케이션 (예: Express 또는 Fastify)을 사용한 예제를 보여주지만, ValidationPipe는 사용되는 전송 방식에 관계없이 WebSockets 및 마이크로서비스에서도 동일하게 작동합니다.

더 알아보기#

class-validator 패키지에서 제공하는 사용자 정의 유효성 검사기, 오류 메시지 및 사용 가능한 데코레이터에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

Support us

Nest is an MIT-licensed open source project. It can grow thanks to the support by these awesome people. If you'd like to join them, please read more here.

Principal Sponsors

Trilon LogoMojam Logo

Sponsors / Partners

Become a sponsor