DTO (Data Transfer Object) в экосистеме JavaScript и TypeScript представляет собой структурированную модель данных, предназначенную для передачи информации между слоями приложения. В контексте серверной разработки DTO выступает формальным контрактом входящих и исходящих данных, определяя форму объекта до того, как он попадёт в бизнес-логику.
Основная задача DTO заключается в изоляции внутренней модели системы от внешних запросов. Любой входящий payload рассматривается как потенциально некорректный или неполный, поэтому требуется явное описание структуры, типов и ограничений.
В связке с библиотекой class-validator DTO приобретает поведенческую составляющую: становится не просто структурой, а объектом с правилами проверки.
Библиотека class-validator работает через декораторы, которые добавляются к свойствам классов. Эти декораторы описывают правила проверки, применяемые к экземпляру класса.
Пример минимального DTO:
import { IsString, IsInt } fr om 'class-validator';
export class CreateUserDto {
@IsString()
username: string;
@IsInt()
age: number;
}
Каждое поле снабжается набором ограничений, которые проверяются во
время выполнения. Валидация осуществляется через функцию
validate:
import { validate } from 'class-validator';
const dto = new CreateUserDto();
dto.username = 'alex';
dto.age = 25;
const errors = await validate(dto);
Результатом выполнения становится массив ошибок. Пустой массив означает прохождение всех проверок.
JSON-запросы не содержат экземпляров классов. Они представляют собой обычные объекты. Для применения class-validator требуется преобразование plain object в class instance.
Используется class-transformer:
import { plainToInstance } from 'class-transformer';
const dto = plainToInstance(CreateUserDto, requestBody);
Только после этого становятся доступными декораторы class-validator, поскольку они работают с метаданными классов.
Набор стандартных декораторов class-validator охватывает большинство типовых проверок.
import { IsString, Length, IsNotEmpty } from 'class-validator';
export class UserDto {
@IsString()
@IsNotEmpty()
@Length(3, 20)
username: string;
}
Основные ограничения:
IsString — проверка строкового типаIsNotEmpty — запрет пустых значенийLength — диапазон длиныimport { IsInt, Min, Max } from 'class-validator';
export class AgeDto {
@IsInt()
@Min(0)
@Max(120)
age: number;
}
Числовая валидация часто используется для ограничений бизнес-уровня, но может быть вынесена в DTO для раннего отсечения некорректных данных.
import { IsBoolean } from 'class-validator';
export class FlagDto {
@IsBoolean()
isActive: boolean;
}
import { IsEnum } from 'class-validator';
enum Role {
USER = 'user',
ADMIN = 'admin',
}
export class RoleDto {
@IsEnum(Role)
role: Role;
}
Enum-валидация фиксирует допустимое множество значений и предотвращает появление произвольных строк.
DTO часто описывает частично заполняемые структуры. В таких случаях
применяется IsOptional.
import { IsOptional, IsString } from 'class-validator';
export class UpdateUserDto {
@IsOptional()
@IsString()
username?: string;
}
IsOptional отключает дальнейшие проверки, если значение
отсутствует. Важно учитывать порядок декораторов: сначала определяется
опциональность, затем типовые ограничения.
Сложные структуры требуют вложенных объектов. Для корректной
валидации применяется комбинация ValidateNested и
Type.
import { ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
@IsString()
city: string;
@IsString()
street: string;
}
export class UserWithAddressDto {
@IsString()
name: string;
@ValidateNested()
@Type(() => AddressDto)
address: AddressDto;
}
ValidateNested активирует проверку внутреннего объекта,
а Type обеспечивает корректное преобразование plain object
в экземпляр класса.
Работа с коллекциями требует комбинирования декораторов.
import { IsArray, IsString } from 'class-validator';
export class TagsDto {
@IsArray()
@IsString({ each: true })
tags: string[];
}
Параметр each: true применяет правило ко всем элементам
массива.
HTTP-запросы передают данные в виде строк, даже если логически они
являются числами или булевыми значениями. Для автоматической
трансформации используется class-transformer.
import { Type } from 'class-transformer';
import { IsInt } from 'class-validator';
export class PaginationDto {
@Type(() => Number)
@IsInt()
page: number;
@Type(() => Number)
@IsInt()
lim it: number;
}
Без трансформации значения "10" и "20"
останутся строками и провалят проверку IsInt.
Группы позволяют применять разные правила в зависимости от сценария использования DTO.
import { IsString } from 'class-validator';
export class UserDto {
@IsString({ groups: ['create'] })
username: string;
@IsString({ groups: ['update'] })
nickname: string;
}
При вызове валидации указывается группа:
validate(dto, { groups: ['create'] });
Это позволяет использовать один класс для разных операций.
Стандартных проверок недостаточно для сложной бизнес-логики. class-validator поддерживает создание пользовательских декораторов.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
registerDecorator,
} from 'class-validator';
@ValidatorConstraint({ async: false })
class IsEvenConstraint implements ValidatorConstraintInterface {
validate(value: number) {
return value % 2 === 0;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} должно быть чётным числом`;
}
}
export function IsEven() {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName,
validator: IsEvenConstraint,
});
};
}
Использование:
export class NumberDto {
@IsEven()
value: number;
}
Результат validate представляет собой массив объектов
ошибок. Каждый объект содержит:
Типичная структура:
[
{
property: 'username',
constraints: {
isString: 'username must be a string',
},
},
]
При интеграции в серверный фреймворк этот массив часто преобразуется в HTTP-ответ с кодом 400.
В системах, использующих DTO как границу входа, применяется дополнительная фильтрация:
whitelist: true — удаляет неизвестные поляforbidNonWhitelisted: true — вызывает ошибку при
наличии лишних полейtransform: true — автоматически преобразует payload в
DTOЭти параметры формируют строгий контракт данных, исключающий неописанные свойства.
class-validator различает отсутствие значения и явное значение
null.
undefined часто обрабатывается через
IsOptionalnull требует явной обработки через
IsDefined или кастомные правилаimport { IsDefined, IsString } from 'class-validator';
export class StrictDto {
@IsDefined()
@IsString()
field: string;
}
DTO обычно располагаются на границе контроллера и сервисного слоя. Их задача заключается в том, чтобы:
В такой архитектуре сервисный слой не занимается проверкой типов и ограничений, получая уже нормализованные данные.
class-validator использует reflect-metadata для хранения
информации о декораторах. Это означает:
Валидация больших графов объектов может стать затратной операцией, особенно при рекурсивных структурах.
Часто встречающиеся проблемы:
plainToInstance, из-за чего декораторы не
срабатываютIsOptional должен
идти первым)each: true для массивовЭти ошибки приводят к тому, что валидация формально присутствует, но фактически не выполняется.
DTO удобно комбинировать, переиспользуя общие части:
class BaseUserDto {
@IsString()
name: string;
}
class ExtendedUserDto extends BaseUserDto {
@IsString()
role: string;
}
Такой подход снижает дублирование и упрощает поддержку контрактов данных при росте системы.