Глава 47. API с нуля: REST, методы, ошибки

Зачем эта глава

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

REST: базовые идеи

  • GET — получить данные.
  • POST — создать данные.
  • PUT/PATCH — обновить.
  • DELETE — удалить.

Пример запроса

JS
const res = await fetch("/api/products");
const data = await res.json();

Обработка ошибок

JS
if (!res.ok) {
  throw new Error("Ошибка запроса");
}
Важно: всегда показывай пользователю состояние загрузки и ошибку.

Пагинация и фильтры

Реальные API часто возвращают данные частями. Это экономит трафик и ускоряет загрузку.

JS
const res = await fetch(
  "https://api.example.com/products?page=2&limit=10&search=phone"
);
const data = await res.json();

На фронте обычно есть кнопки «Следующая/Предыдущая» или бесконечная прокрутка.

Авторизация: Bearer токен

Когда API защищено, запросы делаются с токеном в заголовках.

JS
const res = await fetch("/api/profile", {
  headers: {
    Authorization: `Bearer ${token}`
  }
});
Подсказка: токен нельзя хранить в открытом виде в коде. Используй безопасное хранилище и HTTPS.

OAuth 2.0 + Refresh tokens (реальный поток)

В реальных проектах access‑токен живёт недолго, а refresh‑токен позволяет получить новый. Для фронтенда чаще используют OAuth с PKCE (безопаснее для браузера).

  • Access token — короткий (5–15 минут).
  • Refresh token — длинный (дни/недели).
  • PKCE — защита для публичных клиентов (SPA).

Схема потока

  1. Пользователь логинится → получаем access + refresh.
  2. Отправляем access в запросах к API.
  3. При 401 делаем запрос /refresh и обновляем access.
JS (обёртка fetch)
async function apiFetch(url, options = {}) {
  let res = await fetch(url, options);
  if (res.status !== 401) return res;

  // 1) пробуем обновить токен
  const refreshRes = await fetch("/api/refresh", { method: "POST" });
  if (!refreshRes.ok) return res;

  // 2) повторяем запрос с новым access
  const token = await refreshRes.json();
  const headers = { ...(options.headers || {}), Authorization: `Bearer ${token.access}` };
  return fetch(url, { ...options, headers });
}
Важно: refresh‑токен лучше хранить в httpOnly cookie, а не в localStorage.

OAuth для публичных API: GitHub/Google (PKCE)

Принцип одинаковый: сначала отправляем пользователя на страницу авторизации, затем получаем code и обмениваем его на токены.

  1. Генерируем code_verifier и code_challenge.
  2. Перенаправляем пользователя на /authorize с параметрами.
  3. Получаем code и отправляем его на /token.
JS (PKCE helper)
function base64url(input) {
  return btoa(String.fromCharCode(...new Uint8Array(input)))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

async function createPkcePair() {
  const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
  const data = new TextEncoder().encode(verifier);
  const hash = await crypto.subtle.digest("SHA-256", data);
  const challenge = base64url(hash);
  return { verifier, challenge };
}
JS (redirect to OAuth)
const { verifier, challenge } = await createPkcePair();
sessionStorage.setItem("pkce_verifier", verifier);

const params = new URLSearchParams({
  client_id: "YOUR_CLIENT_ID",
  redirect_uri: "https://your-app.com/auth/callback",
  response_type: "code",
  scope: "profile email",
  code_challenge: challenge,
  code_challenge_method: "S256"
});

location.href = `https://provider.com/oauth/authorize?${params}`;
JS (exchange code → token)
const code = new URLSearchParams(location.search).get("code");
const verifier = sessionStorage.getItem("pkce_verifier");

await fetch("https://provider.com/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    client_id: "YOUR_CLIENT_ID",
    redirect_uri: "https://your-app.com/auth/callback",
    code,
    code_verifier: verifier
  })
});
Подсказка: реальные URL и scope зависят от провайдера (Google/GitHub). Проверяй их документацию.

Задания

Задание: REST-запрос

  • Сделай GET-запрос к публичному API.
  • Добавь обработку ошибок.
  • Покажи индикатор загрузки.