Встроенная поддержка ValidationPipe

NestJS предоставляет встроенный механизм валидации входящих данных через ValidationPipe, который тесно интегрирован с class-validator и class-transformer. Этот механизм является ключевым элементом обработки DTO-слоя и обеспечивает декларативную проверку входных структур без необходимости ручной валидации в контроллерах.

Роль ValidationPipe в архитектуре обработки запросов

ValidationPipe функционирует на уровне пайпов NestJS и применяется к входным данным до их попадания в бизнес-логику контроллеров. Основная задача — преобразование и проверка данных согласно описанным правилам DTO-классов.

В типичной цепочке обработки запросов:

  • запрос поступает в контроллер
  • данные проходят через пайпы
  • выполняется трансформация plain object → class instance
  • выполняется валидация через class-validator
  • при успешной проверке данные передаются дальше

Таким образом, ValidationPipe выступает как фильтр, обеспечивающий структурную целостность данных.

Включение глобальной валидации

Наиболее распространённый сценарий — подключение пайпа на уровне всего приложения:

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

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

  await app.listen(3000);
}
bootstrap();

В этом режиме каждый входящий запрос проходит через единый механизм проверки, что исключает необходимость дублирования логики в контроллерах.

Трансформация данных и class-transformer

Опция transform: true включает автоматическое преобразование входных объектов в экземпляры классов DTO. Это критически важно для корректной работы декораторов class-validator.

Пример DTO:

import { IsInt, IsString } from 'class-validator';

export class CreateUserDto {
  @IsString()
  name: string;

  @IsInt()
  age: number;
}

Без трансформации входные данные остаются обычными объектами, что ограничивает возможности валидации и типизации.

С включённой трансформацией:

@Post()
create(@Body() dto: CreateUserDto) {
  return dto;
}

dto становится экземпляром CreateUserDto, что позволяет корректно применять правила декораторов.

Whitelist и фильтрация лишних полей

Опция whitelist: true автоматически удаляет все свойства, которые не описаны в DTO-классе.

new ValidationPipe({
  whitelist: true,
});

Пример поведения:

Входные данные:

{
  "name": "Alex",
  "age": 30,
  "role": "admin"
}

DTO не содержит role, поэтому поле будет удалено до передачи в контроллер.

Strict режим через forbidNonWhitelisted

Опция forbidNonWhitelisted: true усиливает поведение whitelist. Вместо удаления лишних полей генерируется ошибка валидации.

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

При наличии лишних свойств выполнение запроса прерывается с ошибкой BadRequestException.

Обработка ошибок валидации

ValidationPipe использует стандартный механизм исключений NestJS. Ошибки формируются на основе результата class-validator и возвращаются в структурированном виде.

Пример ответа:

{
  "statusCode": 400,
  "message": [
    "name must be a string",
    "age must be an integer number"
  ],
  "error": "Bad Request"
}

Кастомизация exceptionFactory

Для изменения структуры ошибок используется exceptionFactory:

new ValidationPipe({
  exceptionFactory: (errors) => {
    return new Error(
      JSON.stringify(
        errors.map(err => ({
          property: err.property,
          constraints: err.constraints,
        })),
      ),
    );
  },
});

Этот механизм позволяет унифицировать формат ошибок под требования внешних API или фронтенда.

Тонкая настройка преобразования типов

При включённой трансформации можно использовать дополнительные параметры:

new ValidationPipe({
  transform: true,
  transformOptions: {
    enableImplicitConversion: true,
  },
});

enableImplicitConversion позволяет автоматически преобразовывать примитивы:

  • строка → число
  • строка → boolean
  • строка → Date

Это особенно важно при обработке query-параметров HTTP-запросов, где все значения приходят в виде строк.

Валидация query, params и body

ValidationPipe одинаково применяется ко всем источникам данных:

@Get(':id')
findOne(@Param() params: GetUserParamsDto) {
  return params;
}

@Get()
search(@Query() query: SearchDto) {
  return query;
}

@Post()
create(@Body() body: CreateUserDto) {
  return body;
}

Каждый DTO валидируется независимо, но через единый механизм пайпа.

Локальное применение ValidationPipe

Помимо глобальной настройки, пайп может применяться точечно:

@Post()
create(
  @Body(new ValidationPipe()) dto: CreateUserDto,
) {
  return dto;
}

Локальное использование применяется при необходимости различной логики валидации для разных маршрутов.

Поведение при вложенных объектах

ValidationPipe поддерживает глубокую валидацию вложенных структур при использовании @ValidateNested() и @Type():

import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';

class AddressDto {
  @IsString()
  city: string;
}

class UserDto {
  @ValidateNested()
  @Type(() => AddressDto)
  address: AddressDto;
}

Без @Type() вложенная валидация не активируется, поскольку отсутствует корректная трансформация типов.

Массивы DTO и валидация коллекций

Для работы с массивами используется @Type(() => Class):

class CreateUsersDto {
  @ValidateNested({ each: true })
  @Type(() => CreateUserDto)
  users: CreateUserDto[];
}

Каждый элемент массива проходит отдельную проверку.

Производительность и оптимизация

Использование ValidationPipe добавляет вычислительную нагрузку из-за:

  • рефлексии метаданных
  • трансформации объектов
  • выполнения декораторов class-validator

Оптимизация достигается через:

  • отключение transform при ненужности
  • ограничение глубокой валидации
  • использование DTO только на границе системы

Типичные ошибки интеграции

Наиболее частые проблемы связаны с:

  • отсутствием emitDecoratorMetadata в TypeScript
  • забытым @Type() для вложенных объектов
  • несоответствием типов в DTO и входных данных
  • отключённой трансформацией при использовании class-validator

Каждая из этих ошибок приводит к тому, что валидация либо не срабатывает, либо работает частично.

Связь ValidationPipe и архитектуры DTO

ValidationPipe формирует основу строгого контрактного подхода к данным. DTO становятся единственным источником правды о структуре входящих данных, а pipeline обработки запроса превращается в последовательность предсказуемых преобразований и проверок.