Библиотека class-validator строится вокруг декларативной
модели, основанной на классах и декораторах. В отличие от функциональных
валидаторов, где схема описывается отдельным объектом или цепочкой
вызовов, здесь правила валидации привязываются к свойствам классов через
аннотации метаданных.
Ключевая особенность архитектуры заключается в том, что данные представляются как экземпляры классов, а не как произвольные объекты. Это определяет основной принцип миграции: переход от схем и функций к структурированным DTO-моделям.
Основные различия подходов:
Перенос логики требует перераспределения ответственности: часть правил перемещается из функций в модель данных.
В большинстве библиотек валидации схема отделена от данных. В
class-validator схема становится частью структуры
данных.
Пример базовой модели:
import { IsString, IsInt, Min } from "class-validator";
class UserDto {
@IsString()
name: string;
@IsInt()
@Min(0)
age: number;
}
Эквивалент в Joi:
const schema = Joi.object({
name: Joi.string().required(),
age: Joi.number().integer().min(0)
});
Ключевое изменение при миграции заключается в том, что описание структуры переносится из внешнего объекта в типизированный класс.
| Joi | class-validator |
|---|---|
Joi.string() |
@IsString() |
Joi.number() |
@IsNumber() |
Joi.boolean() |
@IsBoolean() |
Joi.array() |
@IsArray() |
Joi.object() |
вложенный класс |
В Joi:
Joi.string().required()
В class-validator:
import { IsDefined, IsString } from "class-validator";
class ExampleDto {
@IsDefined()
@IsString()
value: string;
}
Или через @IsOptional() для обратной логики.
Joi:
Joi.object({
user: Joi.object({
name: Joi.string()
})
});
class-validator:
import { ValidateNested, IsString } from "class-validator";
import { Type } from "class-transformer";
class User {
@IsString()
name: string;
}
class RootDto {
@ValidateNested()
@Type(() => User)
user: User;
}
Здесь добавляется важная зависимость: class-transformer,
обеспечивающая корректную десериализацию вложенных объектов.
Joi:
Joi.array().items(Joi.string())
class-validator:
import { IsArray, IsString } from "class-validator";
class ArrayDto {
@IsArray()
@IsString({ each: true })
items: string[];
}
Особенность миграции заключается в необходимости явно указывать
проверку каждого элемента через параметр each.
Yup использует цепочки методов и императивно-декларативный стиль.
Yup:
Yup.string().required().min(3)
class-validator:
import { IsString, MinLength } from "class-validator";
class Dto {
@IsString()
@MinLength(3)
value: string;
}
Yup:
Yup.string().nullable().optional()
class-validator:
import { IsOptional, IsString } from "class-validator";
class Dto {
@IsOptional()
@IsString()
value?: string;
}
Разделение optional и nullable в
class-validator требует отдельного понимания:
@IsOptional() — значение может отсутствовать@IsDefined() — значение обязательноnull требует дополнительных кастомных валидаторовYup:
Yup.string().test("check", "error", value => value === "ok")
class-validator:
import { registerDecorator, ValidationArguments } from "class-validator";
function IsOk() {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsOk",
target: object.constructor,
propertyName,
validator: {
validate(value: any) {
return value === "ok";
},
defaultMessage(args: ValidationArguments) {
return "value must be ok";
}
}
});
};
}
express-validator работает через middleware и цепочки проверок запроса.
check("email").isEmail().notEmpty()
import { IsEmail, IsNotEmpty } from "class-validator";
class LoginDto {
@IsEmail()
@IsNotEmpty()
email: string;
}
express-validator:
reqclass-validator:
express-validator:
app.post("/login", [
check("email").isEmail()
], handler);
class-validator (типичный паттерн):
const dto = plainToInstance(LoginDto, req.body);
const errors = await validate(dto);
Сдвиг происходит от middleware-ориентированной модели к объектной модели данных.
AJV основан на JSON Schema, где структура описывается декларативным JSON-объектом.
{
"type": "object",
"properties": {
"age": { "type": "integer", "minimum": 0 }
}
}
import { IsInt, Min } from "class-validator";
class Dto {
@IsInt()
@Min(0)
age: number;
}
AJV:
class-validator:
Важный аспект миграции — преобразование типов.
В большинстве библиотек входные данные остаются «сырыми». В class-validator используется дополнительный слой:
class-transformerplainToInstance()Пример:
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
const dto = plainToInstance(UserDto, request.body);
const result = await validate(dto);
Без преобразования декораторы могут работать некорректно, особенно для чисел и вложенных объектов.
Входные данные HTTP часто приходят как строки.
Joi и Yup автоматически приводят типы при включённом coercion.
class-validator этого не делает по умолчанию.
Решение:
import { Type } from "class-transformer";
import { IsInt } from "class-validator";
class Dto {
@Type(() => Number)
@IsInt()
age: number;
}
Разные библиотеки по-разному трактуют:
""nullundefinedclass-validator требует явного определения поведения через комбинации:
@IsOptional()@IsNotEmpty()В Joi:
Joi.when("role", {
is: "admin",
then: Joi.string().required()
})
В class-validator используется кастомная логика:
import { ValidateIf, IsString } from "class-validator";
class Dto {
role: string;
@ValidateIf(o => o.role === "admin")
@IsString()
adminCode: string;
}
class-validator реализует межполевая проверка через:
@ValidateIfimport { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
@ValidatorConstraint({ name: "match", async: false })
class MatchConstraint implements ValidatorConstraintInterface {
validate(value: any, args: any) {
const [relatedProperty] = args.constraints;
return value === args.object[relatedProperty];
}
}
При переходе с функциональных схем структура проекта изменяется:
Типовая структура:
dto/validators/pipes/ (в связке с NestJS-подобной архитектурой)Без class-transformer числовые и вложенные значения
остаются строками.
each для
массивовПроверка массива без each приводит к валидации только
контейнера.
undefined vs nullПоведение отличается от Joi/Yup и требует явной настройки.
Передача plain-object без plainToInstance приводит к
пропуску декораторов.
| Подход | Модель |
|---|---|
| Joi / Yup | схема как функция |
| AJV | JSON Schema |
| express-validator | middleware chain |
| class-validator | объектно-декларативная модель |
Смена библиотеки означает не только замену API, но и перестройку слоя обработки входных данных.