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

Базовая конфигурация и точка расширения поведения

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

Основная конфигурация строится вокруг следующей структуры:

new ValidationPipe({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: false,
  disableErrorMessages: false,
  validationError: { target: false },
});

Каждый параметр изменяет поведение пайпа на этапе обработки входящих запросов до попадания данных в контроллер.


Трансформация входных данных (transform)

Опция transform активирует автоматическое преобразование plain-объектов в экземпляры классов DTO.

new ValidationPipe({
  transform: true,
});

Механизм основан на class-validator и class-transformer, которые совместно обеспечивают:

  • приведение типов (string → number)
  • создание экземпляров классов
  • применение декораторов валидации

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

class CreateUserDto {
  name: string;
  age: number;
}

При включённом transform:

{ name: "Alex", age: "25" }

становится:

CreateUserDto { name: "Alex", age: 25 }

Это критично для корректной работы числовых и булевых валидаторов.


Белый список полей (whitelist)

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

new ValidationPipe({
  whitelist: true,
});

DTO:

class CreateUserDto {
  name: string;
}

Вход:

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

Результат после валидации:

{
  "name": "Alex"
}

Механизм опирается на метаданные, которые генерируются декораторами class-validator.


Запрет лишних полей (forbidNonWhitelisted)

Расширение whitelist — строгий режим обработки входных данных.

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

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

Поведение:

  • без forbidNonWhitelisted → поле удаляется
  • с forbidNonWhitelisted → ошибка 400

Используется в системах, где важна строгая контрактность API.


Глобальная кастомизация ошибок через exceptionFactory

Стандартный формат ошибок в NestJS может быть адаптирован через exceptionFactory.

new ValidationPipe({
  exceptionFactory: (errors) => {
    const formatted = errors.map(err => ({
      field: err.property,
      constraints: err.constraints,
    }));

    return new BadRequestException(formatted);
  },
});

Структура ValidationError из class-validator содержит:

  • property — имя поля
  • constraints — список нарушений
  • children — вложенные ошибки
  • value — исходное значение

Это позволяет стандартизировать API-ответы под внутренние контракты.


Управление выводом ошибок (validationError)

Опция validationError регулирует, какие данные включаются в ответ:

new ValidationPipe({
  validationError: {
    target: false,
    value: false,
  },
});

Параметры:

  • target — исключает исходный объект DTO
  • value — исключает некорректное значение

Это снижает утечку внутренних данных и уменьшает размер ответа.


Прерывание валидации (stopAtFirstError)

Опция позволяет останавливать проверку при первой ошибке:

new ValidationPipe({
  stopAtFirstError: true,
});

Поведение:

  • false — собираются все ошибки
  • true — возвращается только первая

Внутри class-validator это уменьшает количество проходов по цепочке декораторов.


Группы валидации (groups)

Механизм групп позволяет применять разные правила для разных сценариев.

DTO:

class UserDto {
  @IsString({ groups: ['create'] })
  name: string;

  @IsOptional({ groups: ['update'] })
  @IsString()
  email?: string;
}

Использование:

new ValidationPipe({
  groups: ['create'],
});

Группы позволяют использовать один DTO для разных операций:

  • create
  • update
  • partial update

Применение whitelist совместно с transform

Комбинация transform + whitelist является базовой конфигурацией для строгих API.

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

Результат:

  • удаление лишних полей
  • автоматическое приведение типов
  • защита от неконтролируемых входных данных

Эта комбинация наиболее тесно связана с корректной работой декораторов class-validator.


Кастомные исключения и форматирование ошибок

При сложных доменных системах стандартный формат ошибок становится недостаточным.

Расширенная трансформация:

function flattenErrors(errors: ValidationError[]) {
  return errors.flatMap(err =>
    Object.values(err.constraints || {})
  );
}

new ValidationPipe({
  exceptionFactory: (errors) =>
    new BadRequestException({
      messages: flattenErrors(errors),
    }),
});

Подход применяется для:

  • API с единым форматом ошибок
  • интеграции с фронтенд-валидаторами
  • логирования нарушений схемы

Валидация вложенных объектов

class-validator поддерживает глубокую проверку структур через @ValidateNested.

DTO:

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

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

Для корректной работы требуется:

new ValidationPipe({
  transform: true,
});

Без transform вложенные классы не будут корректно инстанцированы.


Строгая схема входных данных

Комбинация параметров для максимально строгого режима:

new ValidationPipe({
  transform: true,
  whitelist: true,
  forbidNonWhitelisted: true,
  stopAtFirstError: false,
  disableErrorMessages: false,
});

Такой режим обеспечивает:

  • полное соответствие DTO
  • детальную диагностику ошибок
  • контроль структуры входных данных
  • предсказуемое поведение API

Переопределение поведения на уровне контроллера

Конфигурация пайпа может быть локальной:

@Post()
@UsePipes(new ValidationPipe({ transform: true }))
create(@Body() dto: CreateUserDto) {}

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


Интеграция с кастомными валидаторами

class-validator позволяет создавать пользовательские декораторы:

@ValidatorConstraint({ name: 'isEven', async: false })
export class IsEvenConstraint {
  validate(value: number) {
    return value % 2 === 0;
  }
}

Использование в DTO:

@Validate(IsEvenConstraint)
value: number;

ValidationPipe автоматически учитывает такие правила без дополнительных настроек.


Поведение при ошибках трансформации типов

При включённом transform возможны ошибки преобразования:

  • “abc” → number
  • “true” → boolean

Такие ошибки обрабатываются как validation errors внутри пайпа.

Можно контролировать поведение через кастомный exception factory:

exceptionFactory: (errors) => {
  return new BadRequestException({
    type: 'validation_error',
    details: errors,
  });
}

Производственные конфигурации

Типичные профили использования:

Минимальный режим (разработка):

new ValidationPipe({
  whitelist: false,
  transform: false,
});

Строгий режим (production):

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

API-first режим:

new ValidationPipe({
  transform: true,
  whitelist: true,
  exceptionFactory: customFormatter,
});

Взаимодействие с метаданными TypeScript

Работа пайпа основана на метаданных, генерируемых декораторами. Без включённого emitDecoratorMetadata в TypeScript часть функциональности становится недоступной, особенно:

  • автоматическое определение типов
  • корректная трансформация вложенных объектов
  • работа числовых и булевых преобразований

Оптимизация поведения в больших системах

При масштабировании системы валидации ключевыми становятся:

  • минимизация глубины вложенных DTO
  • ограничение числа валидаторов на поле
  • использование stopAtFirstError в high-load сценариях
  • централизованное форматирование ошибок через exceptionFactory

Все эти механизмы позволяют контролировать поведение ValidationPipe без изменения бизнес-логики контроллеров.