Типизация ответов API

При работе с API в TypeScript важно не только получить данные, но и правильно описать их структуру. Это позволяет избежать ошибок на этапе разработки и сделать код более надёжным и читаемым.

Определение интерфейса ответа

Первый шаг — создать интерфейс, описывающий ожидаемую структуру данных от сервера. Например, если API возвращает информацию о пользователе:

interface User {
  id: number;
  name: string;
  email: string;
}

Этот интерфейс можно использовать для типизации переменных, параметров функций и возвращаемых значений.

Приведение типа при получении данных

Метод response.json() возвращает промис с типом any, что отключает проверку типов. Чтобы восстановить безопасность, явно указывают ожидаемый тип:

const response = await fetch('/api/user/1');
const user: User = await response.json();

Теперь при обращении к user.name TypeScript знает, что это строка, а user.id — число. Если попытаться обратиться к несуществующему полю, например user.username, компилятор выдаст ошибку.

Почему типизация важна

Без типов легко допустить ошибку, особенно если структура ответа меняется или отличается от документации. Например, сервер может вернуть:

{ "id": 1, "username": "alex" }

а в коде ожидается:

interface User { name: string; }

Если не использовать типизацию, обращение к user.name завершится undefined, и ошибка проявится только во время выполнения. С интерфейсом TypeScript предупредит об этом ещё до запуска.

Безопасная обработка неизвестных данных

На практике нельзя полностью доверять серверу. Даже при наличии интерфейса реальный ответ может отличаться. Поэтому в production-коде рекомендуется сначала считать ответ типа unknown, а затем проверить его структуру.

Пример безопасного парсинга:

const response = await fetch('/api/user/1');
const rawData: unknown = await response.json();

if (
  typeof rawData === 'object' &&
  rawData !== null &&
  'id' in rawData &&
  'name' in rawData &&
  typeof (rawData as any).id === 'number' &&
  typeof (rawData as any).name === 'string'
) {
  const user: User = rawData as User;
  console.log(user.name);
} else {
  throw new Error('Invalid user data received from server');
}

Такой подход называется проверкой времени выполнения (runtime validation). Он сочетается со статической типизацией и обеспечивает защиту как на этапе разработки, так и при работе приложения.

Для упрощения таких проверок в реальных проектах часто используют библиотеки вроде Zod или io-ts, но в рамках курса достаточно ручной проверки через условия и приведение типов.