В основе работы с class-validator лежит идея декларативного описания правил валидации через классы и декораторы. Класс выступает структурой данных, а его свойства — точками контроля входящей информации. Такой подход позволяет отделить бизнес-логику от проверки корректности данных и делает код предсказуемым и расширяемым.
Класс для валидации представляет собой обычный TypeScript/JavaScript класс, свойства которого аннотируются декораторами из библиотеки class-validator. Каждый декоратор описывает правило, которому должно соответствовать значение поля.
Простейший пример:
import { IsString, IsInt } from 'class-validator';
class CreateUserDto {
@IsString()
name: string;
@IsInt()
age: number;
}
В этом примере класс CreateUserDto не содержит логики
проверки. Он лишь описывает структуру данных. Проверка происходит
отдельно через функцию validate.
Классы валидации часто называют DTO (Data Transfer Object). Их задача — формализовать контракт входных данных между слоями приложения. Такой класс:
DTO-класс не должен содержать методов, изменяющих состояние или реализующих поведение. Его назначение — описание формы данных.
Для работы декораторов требуется включение поддержки в TypeScript:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
Также необходимо установить reflect-metadata:
import 'reflect-metadata';
Без этого механизма class-validator не сможет анализировать типы свойств и корректно применять часть правил.
На практике класс начинается с определения простых полей:
import { IsString, IsNumber, IsBoolean } from 'class-validator';
class ProductDto {
@IsString()
title: string;
@IsNumber()
price: number;
@IsBoolean()
inStock: boolean;
}
Каждый декоратор описывает ожидаемый тип. Если входящее значение не соответствует типу, валидация вернёт ошибку.
По умолчанию поля считаются обязательными. Для управления этим
поведением используется @IsOptional().
import { IsOptional, IsString } from 'class-validator';
class UpdateProductDto {
@IsOptional()
@IsString()
title?: string;
}
Порядок декораторов важен: сначала @IsOptional(), затем
остальные проверки. Это означает, что остальные валидаторы будут
применяться только если значение присутствует.
Для строк часто применяются дополнительные ограничения:
import { IsString, Length, Matches } from 'class-validator';
class UserDto {
@IsString()
@Length(3, 20)
username: string;
@Matches(/^[a-z0-9_]+$/)
nickname: string;
}
@Length(min, max) задаёт допустимую длину строки.
@Matches() позволяет ограничить формат с помощью
регулярного выражения.
Для чисел применяются специализированные декораторы:
import { IsNumber, Min, Max } from 'class-validator';
class PaymentDto {
@IsNumber()
@Min(1)
@Max(10000)
amount: number;
}
Такие ограничения полезны при проверке бизнес-правил на уровне входных данных.
Одним из ключевых механизмов является поддержка вложенной валидации.
Для этого используется @ValidateNested() совместно с
@Type() из class-transformer.
import { ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
city: string;
@IsString()
street: string;
}
class UserDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}
Без @Type() вложенный объект не будет корректно
преобразован в экземпляр класса, и валидация не выполнится
полностью.
Массивы требуют отдельного подхода. Используются комбинации декораторов:
import { IsArray, IsString } from 'class-validator';
class GroupDto {
@IsArray()
@IsString({ each: true })
tags: string[];
}
Параметр each: true указывает, что правило применяется к
каждому элементу массива.
Входные данные часто приходят в виде строк. Для приведения типов используется class-transformer:
import { Type } from 'class-transformer';
import { IsBoolean } from 'class-validator';
class SettingsDto {
@Type(() => Boolean)
@IsBoolean()
isActive: boolean;
}
Такой подход особенно важен при работе с HTTP-запросами, где все значения изначально строковые.
В сложных сценариях применяется условная проверка через
@ValidateIf():
import { ValidateIf, IsString } from 'class-validator';
class SearchDto {
@ValidateIf(o => o.type === 'advanced')
@IsString()
query: string;
}
Поле query проверяется только при выполнении
условия.
При необходимости создаются собственные декораторы:
import { registerDecorator, ValidationOptions } from 'class-validator';
function IsEven(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isEven',
target: object.constructor,
propertyName,
options: validationOptions,
validator: {
validate(value: number) {
return typeof value === 'number' && value % 2 === 0;
},
},
});
};
}
class NumberDto {
@IsEven()
value: number;
}
Такой механизм позволяет расширять систему валидации без модификации библиотеки.
Классы могут наследоваться, что позволяет переиспользовать правила:
class BaseUserDto {
@IsString()
name: string;
}
class ExtendedUserDto extends BaseUserDto {
@IsNumber()
age: number;
}
Все декораторы базового класса сохраняются и участвуют в валидации наследника.
В архитектуре часто требуется создавать вариации одного DTO. Это
достигается через композицию классов и условные поля с
@IsOptional(). Такой подход позволяет избежать дублирования
и поддерживать единый источник правил.
Перед проверкой объект должен быть преобразован в экземпляр класса:
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
const dto = plainToInstance(CreateUserDto, requestBody);
const errors = await validate(dto);
Без преобразования декораторы не будут применены корректно, поскольку проверка работает на уровне экземпляра класса.
Использование TypeScript усиливает систему class-validator, но не заменяет её. Типы исчезают в рантайме, поэтому именно декораторы обеспечивают проверку фактических значений.
Класс валидации становится единственным источником истины для структуры данных на этапе выполнения, обеспечивая согласованность между слоями приложения.