Валидация входящих параметров запроса в серверных 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(), поскольку тип остаётся несовместимым.
Параметры маршрута часто используются для идентификаторов ресурсов. Их проверка критична, так как напрямую влияет на доступ к данным.
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-контроллера валидация параметров обычно разделяется на три независимых слоя:
req.params)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 валидация параметров запроса становится частью промежуточного слоя обработки запроса. Она располагается между сетевым уровнем и бизнес-логикой, обеспечивая гарантированную корректность входных данных до попадания в сервисный слой.
Такой подход снижает необходимость повторных проверок внутри бизнес-логики и упрощает сопровождение кода за счёт централизованного описания правил.