Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як написати типізований API-клієнт на fetch із перевіркою відповідей?

Розкидані по коду fetch з response.json() as User дають і дублювання, і неперевірені дані. Краще один клієнт, що поєднує запит, обробку помилок і перевірку схемою.

import * as z from 'zod';

export class HttpError extends Error {
  constructor(public readonly status: number, public readonly body: unknown) {
    super(`HTTP ${status}`);
    this.name = 'HttpError';
  }
}

export async function apiGet<S extends z.ZodType>(url: string, schema: S): Promise<z.infer<S>> {
  const response = await fetch(url, { headers: { Accept: 'application/json' } });

  if (!response.ok) {
    throw new HttpError(response.status, await response.json().catch(() => null));
  }

  return schema.parse(await response.json());
}

Використання - тип виводиться зі схеми, жодних as:

const user = await apiGet('/api/users/1', UserSchema);
user.email;   // string - і це перевірено

const page = await apiGet('/api/vacancies?page=2', paginated(VacancySchema));

Узагальнена схема для пагінації Laravel:

const paginated = <T extends z.ZodType>(item: T) =>
  z.object({
    data: z.array(item),
    meta: z.object({ current_page: z.number(), last_page: z.number(), total: z.number() }),
  });

Чому параметр типу S extends z.ZodType, а не T: тип результату виводиться з переданої схеми. Варіант apiGet<T>(url): Promise<T> без схеми - це прихований as: виклик apiGet<User>(url) виглядає типізованим, але нічого не перевіряє.

Що ще варто додати в клієнт:

  • CSRF і cookies для Laravel (X-XSRF-TOKEN, credentials), заголовок Accept: application/json, щоб помилки валідації приходили як JSON 422;
  • розбір 422 у типізовану помилку валідації з errors: Record<string, string[]>;
  • скасування через AbortSignal (параметр signal) і тайм-аут;
  • POST/PUT з типізованим тілом: apiPost<In, S>(url, body: In, schema: S).

Пастки:

  • response.json() на порожній відповіді (204 No Content) кидає помилку - обробляти окремо;
  • помилка перевірки схеми - це баг контракту між бекендом і фронтендом, а не помилка користувача. Її варто логувати з деталями (z.prettifyError) у моніторинг, а користувачу показати загальне повідомлення.

Готові варіанти: ky, ofetch з хуками, або генерація клієнта з OpenAPI - тоді й схеми, й типи створюються автоматично.

Докладніше в документації: Generics

Перевір себе

20 випадкових питань за спробу, після завершення - розбір кожної помилки

Схожі питання