Глава 47. API с нуля: REST, методы, ошибки
Зачем эта глава
Почти каждое приложение работает с API. Нужно понимать, как отправлять запросы, читать ответы и обрабатывать ошибки.
REST: базовые идеи
- GET — получить данные.
- POST — создать данные.
- PUT/PATCH — обновить.
- DELETE — удалить.
Пример запроса
const res = await fetch("/api/products");
const data = await res.json();
Обработка ошибок
if (!res.ok) {
throw new Error("Ошибка запроса");
}
Важно: всегда показывай пользователю состояние загрузки и ошибку.
JS
JS
JS (обёртка fetch)
JS (PKCE helper)
JS (redirect to OAuth)
JS (exchange code → token)
Пагинация и фильтры
Реальные API часто возвращают данные частями. Это экономит трафик и ускоряет загрузку.
const res = await fetch(
"https://api.example.com/products?page=2&limit=10&search=phone"
);
const data = await res.json();
На фронте обычно есть кнопки «Следующая/Предыдущая» или бесконечная прокрутка.
Авторизация: Bearer токен
Когда API защищено, запросы делаются с токеном в заголовках.
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).
Схема потока
- Пользователь логинится → получаем access + refresh.
- Отправляем access в запросах к API.
- При 401 делаем запрос /refresh и обновляем access.
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 и обмениваем его на токены.
- Генерируем code_verifier и code_challenge.
- Перенаправляем пользователя на /authorize с параметрами.
- Получаем code и отправляем его на /token.
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 };
}
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}`;
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.
- Добавь обработку ошибок.
- Покажи индикатор загрузки.