Валидация тела запроса

Валидация тела запроса в серверных JavaScript-приложениях строится вокруг идеи строгого описания входных данных и автоматической проверки их соответствия заданным правилам до попадания в бизнес-логику. В экосистеме TypeScript и NestJS наиболее распространённым инструментом становится Class-validator, работающий совместно с class-transformer и механизмами пайпов.

Тело HTTP-запроса рассматривается как неконтролируемый источник данных, требующий формализации структуры. Каждый входной объект приводится к DTO (Data Transfer Object), где свойства описываются через классы и аннотации. Валидация выполняется на уровне экземпляров классов, что позволяет использовать метаданные декораторов и отражение типов.

Основная цель подхода — исключение некорректных данных до выполнения контроллеров и сервисов.

Подключение и базовая настройка

Библиотека устанавливается вместе с зависимостями преобразования объектов:

npm install class-validator class-transformer

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

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

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

  • transform — преобразование plain object в экземпляр класса DTO
  • whitelist — удаление полей, не описанных в DTO
  • forbidNonWhitelisted — генерация ошибки при наличии лишних полей

Описание DTO и базовые декораторы

Структура тела запроса формализуется через класс:

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

class CreateUserDto {
  @IsString()
  @MinLength(3)
  username: string;

  @IsInt()
  age: number;
}

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

Наиболее часто используемые декораторы:

  • IsString — проверка строкового типа
  • IsInt — целочисленное значение
  • IsBoolean — булев тип
  • IsEmail — формат email
  • MinLength / MaxLength — ограничения длины
  • IsOptional — необязательное поле

Преобразование типов и особенности тела запроса

HTTP-запрос передаёт данные в виде строк или JSON-структур, где числовые значения часто приходят как строки. Без преобразования типы могут не соответствовать ожидаемым в DTO.

Использование transform совместно с class-transformer позволяет автоматически приводить данные к нужным типам:

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

class CreateUserDto {
  @Type(() => Number)
  @IsInt()
  age: number;
}

Механизм преобразования критичен для корректной работы числовых и датированных полей.

Валидация числовых значений

Числовые ограничения описываются через специализированные декораторы:

import { IsInt, Min, Max } from 'class-validator';

class ProductDto {
  @IsInt()
  @Min(1)
  @Max(1000)
  price: number;
}

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

Работа со строками

Строковые поля часто требуют комплексной проверки:

import { IsString, Length, Matches } from 'class-validator';

class ProfileDto {
  @IsString()
  @Length(5, 20)
  nickname: string;

  @Matches(/^[a-zA-Z0-9_]+$/)
  username: string;
}

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

Булевы значения и особенности парсинга

Булевы поля часто приходят как строки "true" и "false". Для корректной интерпретации используется преобразование:

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

class SettingsDto {
  @Type(() => Boolean)
  @IsBoolean()
  isActive: boolean;
}

Валидация массивов

Работа с массивами требует комбинирования декораторов:

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

class TagsDto {
  @IsArray()
  @IsString({ each: true })
  tags: string[];
}

Параметр each: true обеспечивает применение правила к каждому элементу коллекции.

Вложенные структуры

Тело запроса часто содержит сложные объекты. Для корректной валидации используется вложенная декларация классов:

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

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

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

Без ValidateNested вложенные объекты не проходят полноценную проверку.

Поведение при лишних полях

Контроль структуры тела запроса реализуется через whitelist-режим. Поля, отсутствующие в DTO, автоматически удаляются либо вызывают ошибку:

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

Такой подход снижает риск проникновения неожиданных данных в систему.

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

При нарушении правил формируется массив ошибок, содержащий:

  • имя поля
  • список ограничений
  • текстовые сообщения

Структура ошибки позволяет централизованно формировать ответ API:

{
  statusCode: 400,
  message: [
    "username must be longer than or equal to 3 characters"
  ],
  error: "Bad Request"
}

Валидационные ошибки возникают до вызова контроллера, что исключает выполнение бизнес-логики при некорректных данных.

Кастомные валидаторы

При необходимости сложной логики создаются собственные правила через интерфейс ValidatorConstraint:

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

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

  defaultMessage(args: ValidationArguments) {
    return `${args.property} must be even`;
  }
}

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

import { Validate } from 'class-validator';

class NumberDto {
  @Validate(IsEvenConstraint)
  value: number;
}

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

Композиция правил

Несколько декораторов могут комбинироваться на одном поле, формируя цепочку ограничений:

class AccountDto {
  @IsString()
  @MinLength(5)
  @MaxLength(30)
  username: string;
}

Каждое правило выполняется последовательно, а итоговая ошибка агрегируется в общий результат валидации.

Работа с необязательными полями

Необязательные свойства исключаются из проверки при отсутствии значения:

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

class UpdateUserDto {
  @IsOptional()
  @IsString()
  bio?: string;
}

Такой подход позволяет использовать единый DTO для частичного обновления данных.

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

При включённом преобразовании данные тела запроса проходят несколько этапов:

  1. Преобразование plain object в экземпляр класса
  2. Применение class-transformer декораторов
  3. Выполнение class-validator правил
  4. Формирование результата или ошибки

Эта цепочка обеспечивает предсказуемую обработку входных данных независимо от исходного формата JSON.

Типовые схемы использования в контроллерах

Контроллеры принимают уже валидированные DTO, не выполняя дополнительной проверки:

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

Таким образом, слой контроллера остаётся тонким и ориентированным на маршрутизацию, а не на обработку данных.

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

При росте количества DTO структура валидации начинает играть роль формального контракта API. Повторное использование классов, композиция и наследование позволяют уменьшить дублирование правил:

class BaseUserDto {
  @IsString()
  username: string;
}

class AdminUserDto extends BaseUserDto {
  @IsString()
  role: string;
}

Наследование декораторов сохраняется, что упрощает поддержку общей логики валидации.

Синхронизация валидации и типов TypeScript

Class-validator не заменяет систему типов TypeScript, а дополняет её на уровне runtime. Типы обеспечивают статическую проверку, тогда как декораторы формируют поведение во время выполнения. Совместное использование позволяет закрыть оба уровня контроля данных.