DTO в TypeScript — интерфейс vs класс
Data Transfer Object — структура для передачи данных между слоями. В Java/C# DTO обязательны как классы. В JS/TS Предлагается использовать
interface/type+ литералы объектов, без классов.
Что это / Зачем в TS
DTO нужен для:
- Контракта на возвращаемые из API данные
- Очистки модели перед передачей (не отдавать
passwordHashклиенту) - Изоляции слоёв (репозиторий → сервис → контроллер)
Подход Java/C#: класс DTO обязателен
// Java: нельзя сделать литерал объекта
public class UserDto {
public Long id;
public String name;
public String email;
}
UserDto dto = new UserDto();
dto.id = 1L;
В этих языках нет литералов объектов — DTO-класс единственный способ описать форму.
Подход автора для TS/JS
"Чтобы возвращать объекты, нам совершенно не обязательно иметь классы. Достаточно интерфейса и литерала."
interface UserDto {
id: number;
name: string;
email: string;
}
function toDto(row: UserRow): UserDto {
return { id: row.id, name: row.name, email: row.email };
}
Пример
// Без класса — литерал + interface
interface OrderDto {
id: string;
total: number;
items: { sku: string; qty: number };
}
const toOrderDto = (o: Order): OrderDto => ({
id: o.id,
total: o.computeTotal,
items: o.items.map((i) => ({ sku: i.sku, qty: i.qty })),
});
Когда использовать / Когда НЕ
- ✅ когда: возвращаешь данные из repository/сервиса
- ✅ когда: нужен явный контракт API-ответа
- ✅ когда: чистка приватных полей перед отдачей наружу
- ❌ когда: использовать классы с конструктором и поведением — это уже не DTO, а модель
- ❌ когда: ради DTO заводить отдельный класс с пустым конструктором (анемичная модель)
Важно: следить за формой объектов
"Если у нас последовательность полей будет расходиться, JavaScript всё пропустит, но всё будет медленнее."
Создавая DTO через литерал, всегда возвращайте поля в одном порядке — V8 кэширует hidden class. Это особенно важно в production-коде, где функция вызывается миллионы раз.
// Плохо — разный порядок ключей при разных условиях
function toDto(o: Order, withMeta: boolean): OrderDto {
if (withMeta) return { id: o.id, meta: o.meta, total: o.total };
return { id: o.id, total: o.total }; // разная форма!
}
// Хорошо — стабильная форма
function toDto(o: Order): OrderDto {
return { id: o.id, total: o.total, meta: o.meta ?? null };
}
Альтернативы
- class DTO — нужно когда есть валидаторы class-validator (NestJS), декораторы.
- Zod/Yup schema — DTO выводится из схемы, плюс рантайм-валидация.
- JSON Schema предпочитает: один источник правды + дешёвая рантайм-проверка.
🎓 Источники
- 🎓 Я запрещаю UNION types в TypeScript и ORM, DTO, Query Builder · 2025-12-17 · YouTube
- Позиция автора: DTO в JS/TS — это
interface+ литерал, без отдельных классов. Классы DTO нужны только в Java/C#, где нет литералов.
- Позиция автора: DTO в JS/TS — это
- 🎓 Анемичная модель и DTO, слои в DDD · 2025-02-09 · YouTube