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

Валидация входящих параметров запроса в серверных JavaScript-приложениях решает задачу контроля структуры и типов данных, поступающих от клиента. Любой HTTP-запрос содержит потенциально недоверенные данные: параметры строки запроса, сегменты URL, тело запроса. Отсутствие строгой проверки приводит к ошибкам выполнения, утечкам логики бизнес-уровня и увеличению поверхности атак.

Библиотека class-validator реализует декларативный подход к описанию правил валидации через классы и декораторы. Основная идея заключается в том, что структура входных данных описывается отдельной моделью, а правила проверки привязываются к её полям.


Типичный подход строится вокруг DTO (Data Transfer Object). DTO представляет собой класс, описывающий ожидаемую форму входных данных.

import {
  IsString,
  IsInt,
  Min,
  Max,
  Length,
  IsOptional
} fr om 'class-validator';

class CreateUserDto {
  @IsString()
  @Length(3, 20)
  username;

  @IsString()
  @Length(8, 50)
  password;

  @IsInt()
  @Min(0)
  @Max(120)
  age;
}

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


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

Параметры query string часто приходят в виде строк, даже если семантически представляют числа или булевы значения. Это требует явного приведения и проверки.

class ListUsersQuery {
  @IsOptional()
  @IsInt()
  page;

  @IsOptional()
  @IsInt()
  lim it;
}

При использовании без дополнительного преобразования значения page=1 и limit=10 будут строками. Поэтому важным элементом становится сочетание валидации и трансформации данных.


Преобразование типов перед валидацией

class-validator сам по себе не выполняет преобразование типов. Для этого используется class-transformer, который часто применяется совместно.

import { plainToInstance } fr om 'class-transformer';
import { validate } from 'class-validator';

const query = plainToInstance(ListUsersQuery, req.query);

validate(query).then(errors => {
  if (errors.length > 0) {
    res.status(400).json(errors);
  }
});

Без преобразования строковые значения не пройдут проверки @IsInt(), поскольку тип остаётся несовместимым.


Валидация параметров URL (path params)

Параметры маршрута часто используются для идентификаторов ресурсов. Их проверка критична, так как напрямую влияет на доступ к данным.

class GetUserParams {
  @IsInt()
  id;
}

В Express такие параметры поступают как строки, например /users/15. Поэтому типизация и преобразование остаются обязательными.

const params = plainToInstance(GetUserParams, req.params);

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

Тело запроса содержит наиболее сложные структуры данных, включая вложенные объекты и массивы. class-validator поддерживает глубокую валидацию через вложенные классы.

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

class ProfileDto {
  @IsString()
  bio;
}

class CreateUserBody {
  @IsString()
  username;

  @ValidateNested()
  @Type(() => ProfileDto)
  profile;
}

Декоратор @ValidateNested() активирует рекурсивную проверку вложенного объекта, а @Type() обеспечивает корректную трансформацию структуры.


Обработка массивов в запросах

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

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

class DeleteUsersDto {
  @IsArray()
  @IsInt({ each: true })
  ids;
}

Ключевой момент заключается в параметре each: true, который включает проверку каждого элемента массива.


Условная валидация параметров

Некоторые поля могут становиться обязательными только при определённых условиях. Для этого используется ValidateIf.

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

class SearchDto {
  @ValidateIf(o => !o.query)
  @IsString()
  fallback;

  @ValidateIf(o => !o.fallback)
  @IsString()
  query;
}

Такая конструкция позволяет задавать взаимоисключающие или зависимые параметры запроса.


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

Часто параметры запроса ограничиваются диапазонами значений, например пагинация или фильтрация.

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

class PaginationDto {
  @IsInt()
  @Min(1)
  page;

  @IsInt()
  @Min(1)
  @Max(100)
  lim it;
}

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


Пользовательские правила валидации

Базовых декораторов недостаточно для бизнес-логики. class-validator позволяет создавать собственные проверки.

import {
  registerDecorator,
  ValidationOptions
} from 'class-validator';

function IsEven(validationOptions) {
  return function (object, propertyName) {
    registerDecorator({
      name: 'isEven',
      target: object.constructor,
      propertyName,
      options: validationOptions,
      validator: {
        validate(value) {
          return typeof value === 'number' && value % 2 === 0;
        }
      }
    });
  };
}

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

class TestDto {
  @IsEven()
  number;
}

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


Группы валидации

Валидация может зависеть от сценария использования одной и той же модели данных. Для этого применяются группы.

import { IsString } from 'class-validator';

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

  @IsString({ groups: ['update'] })
  password;
}

При вызове проверки указывается контекст:

validate(dto, { groups: ['create'] });

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


Игнорирование неизвестных полей

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

const dto = plainToInstance(CreateUserDto, req.body, {
  excludeExtraneousValues: true
});

Это предотвращает попадание неожиданных данных в бизнес-логику.


Композиция валидационных слоёв в HTTP-обработке

На уровне HTTP-контроллера валидация параметров обычно разделяется на три независимых слоя:

  • параметры пути (req.params)
  • query-параметры (req.query)
  • тело запроса (req.body)

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

const params = plainToInstance(GetUserParams, req.params);
const query = plainToInstance(ListUsersQuery, req.query);
const body = plainToInstance(CreateUserBody, req.body);

await validate(params);
await validate(query);
await validate(body);

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


Работа с ошибками валидации

Результатом проверки является массив объектов ошибок, содержащих структуру нарушения правил. Эти данные можно преобразовывать в единый формат ответа API.

Ошибки включают:

  • имя поля
  • значение
  • список нарушенных ограничений

Это позволяет формировать детализированные ответы, пригодные для логирования и диагностики.


Синхронная и асинхронная валидация

class-validator поддерживает оба режима проверки. Синхронная используется для простых правил, асинхронная — для проверок, требующих внешних источников данных (например, базы данных).

validate(dto).then(errors => {
  // обработка
});

Асинхронные валидаторы особенно важны при проверке уникальности значений или существования связанных сущностей.


Интеграция с архитектурой API

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

Такой подход снижает необходимость повторных проверок внутри бизнес-логики и упрощает сопровождение кода за счёт централизованного описания правил.