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, слои в DDD · 2025-02-09 · YouTube

См. также