Библиотеки class-validator и
class-transformer образуют типичный дуэт в экосистеме
работы с DTO-объектами в JavaScript/TypeScript-проектах, особенно в
серверных приложениях на основе NestJS или аналогичных архитектур.
class-validator отвечает за проверку данных через
декораторы валидации, а class-transformer обеспечивает
преобразование обычных объектов (plain objects) в экземпляры классов и
обратно. Совместное использование этих библиотек решает ключевую
проблему: валидация «сырых» входных данных невозможна без
предварительного приведения их к структуре классов.
Основная идея интеграции заключается в следующем:
class-transformer преобразует его в экземпляр
класса.class-validator выполняет проверку на основе
декораторов.Ключевой момент: валидация работает только с экземплярами классов, а не с plain object.
Главный инструмент интеграции — функция
plainToInstance.
import { plainToInstance } from 'class-transformer';
class User {
name: string;
age: number;
}
const plainUser = {
name: 'Alex',
age: 25,
};
const userInstance = plainToInstance(User, plainUser);
После преобразования userInstance становится полноценным
экземпляром класса User, что позволяет применять декораторы
class-validator.
Валидация строится на декораторах, описывающих ограничения для полей.
import { IsString, IsInt, Min, Max } from 'class-validator';
class User {
@IsString()
name: string;
@IsInt()
@Min(0)
@Max(120)
age: number;
}
Без предварительного преобразования через
class-transformer подобная схема не будет работать
корректно при получении данных извне.
На практике интеграция выглядит как последовательность двух операций: трансформация и проверка.
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
class User {
@IsString()
name: string;
@IsInt()
age: number;
}
const input = {
name: 'John',
age: 30,
};
const instance = plainToInstance(User, input);
validate(instance).then(errors => {
if (errors.length > 0) {
console.log('Ошибки валидации');
} else {
console.log('Данные корректны');
}
});
Одной из ключевых точек интеграции является автоматическое приведение типов.
import { plainToInstance } from 'class-transformer';
const instance = plainToInstance(User, input, {
enableImplicitConversion: true,
});
"25" может быть преобразована в
number"true" может быть преобразовано в
booleanВажно: без этой опции class-validator
может получать значения неправильного типа, что приведёт к ложным
ошибкам.
Одной из сильных сторон связки является работа с вложенными DTO.
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
class Address {
@IsString()
city: string;
@IsString()
street: string;
}
class User {
@IsString()
name: string;
@ValidateNested()
@Type(() => Address)
address: Address;
}
Здесь критически важны два механизма:
@Type(() => Address) — сообщает class-transformer,
как создавать вложенный объект@ValidateNested() — активирует рекурсивную
валидациюБез @Type вложенный объект останется plain object, и
валидация не выполнится корректно.
При работе с коллекциями требуется комбинирование @Type
и @ValidateNested({ each: true }).
class Role {
@IsString()
name: string;
}
class User {
@IsString()
username: string;
@ValidateNested({ each: true })
@Type(() => Role)
roles: Role[];
}
Поведение:
Roleclass-transformer предоставляет механизм модификации
значений до валидации.
import { Transform } from 'class-transformer';
class User {
@Transform(({ value }) => value.trim())
@IsString()
name: string;
}
Значение сначала преобразуется, затем проверяется
class-validator.
Критически важный аспект архитектуры:
Plain object поступает в систему
Выполняется class-transformer
@TransformВыполняется class-validator
Ошибка в этом порядке приводит к некорректным результатам валидации.
При интеграции часто требуется фильтрация входных данных.
import { Expose, Exclude } from 'class-transformer';
class User {
@Expose()
name: string;
@Expose()
age: number;
@Exclude()
password: string;
}
Использование:
const instance = plainToInstance(User, input, {
excludeExtraneousValues: true,
});
@Expose() удаляютсяТиповой pipeline:
const dto = plainToInstance(CreateUserDto, request.body, {
enableImplicitConversion: true,
excludeExtraneousValues: true,
});
const errors = await validate(dto);
if (errors.length > 0) {
throw new Error('Validation failed');
}
Здесь объединяются:
class-validator не вызывает
class-transformer автоматически. Преобразование всегда
должно выполняться явно.
Без @Type вложенные структуры остаются plain objects,
что делает @ValidateNested неэффективным.
Без enableImplicitConversion возможны ошибки:
"123" не проходит @IsInt()"false" не распознаётся как booleanclass User {
@Transform(({ value }) => value?.toLowerCase())
@IsString()
email: string;
}
Сначала выполняется нормализация, затем проверка формата.
class A {
@Type(() => B)
@ValidateNested()
data: B;
}
class B {
@IsString()
value: string;
}
При изменении входных данных структура остаётся валидируемой
благодаря Type.
При сложных структурах возможно комбинирование:
class Event {
@ValidateNested({ each: true })
@Type(() => Participant)
participants: Participant[];
}
Каждый элемент массива проходит независимую трансформацию и проверку.
Связка class-transformer и class-validator
формирует слой DTO, который выполняет функции:
Эта комбинация становится промежуточным слоем между транспортом данных (HTTP, RPC, WebSocket) и бизнес-логикой, обеспечивая строгую изоляцию доменной модели от внешних форматов данных.