HTTP-клиент в Node.js: fetch и запросы к API
Содержание
О проверке кода. Примеры не запускались в этой сессии (по договорённости — фокус на контенте, а не прогонах). Говорим прямо, а не ставим бейдж «проверено».
Серверу постоянно нужно ходить к внешним API: платёжные системы, сторонние сервисы, микросервисы между собой. Раньше для этого ставили axios или node-fetch. Теперь Node.js умеет fetch из коробки.
fetch встроен — ничего ставить не надо
// Node 18+ — fetch доступен глобально, без require и npm install
const response = await fetch('https://api.example.ru/users/1');
const user = await response.json();
console.log(user);
С Node 18 fetch — глобальная функция, тот же стандарт, что в браузере. Код запросов выглядит одинаково на клиенте и сервере — это удобно, когда пишете и фронтенд, и бэкенд. Внешние node-fetch и во многих случаях axios стали не нужны для базовых задач.
Та же ловушка: не бросает на 404
// ✅ правильно: проверяем статус вручную
async function getUser(id) {
const response = await fetch(`https://api.example.ru/users/${id}`);
if (!response.ok) {
throw new Error(`API вернул ${response.status}`); // 404, 500 — сами
}
return response.json();
}
fetch отклоняет промис только при сетевом сбое — нет соединения, DNS не разрешился. HTTP-ошибки 404 и 500 для него — успешный ответ. Поэтому статус проверяют через response.ok (подробнее — в браузерной статье). Это отличает fetch от axios, который на ошибочных статусах бросает сам, — частый повод споткнуться при переходе.
POST с JSON
async function createUser(data) {
const response = await fetch('https://api.example.ru/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
Отправка данных — как в браузере: method, заголовок Content-Type, тело с JSON.stringify. Ничего серверно-специфичного здесь нет.
Таймаут — на сервере обязателен
// ❌ без таймаута зависший API заблокирует запрос надолго
const response = await fetch(url);
// ✅ таймаут 5 секунд
try {
const response = await fetch(url, {
signal: AbortSignal.timeout(5000),
});
const data = await response.json();
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Внешний API не ответил за 5 сек');
}
}
На сервере таймаут критичнее, чем в браузере. Внешний API может зависнуть, и без ограничения ваш запрос (а с ним и обработка HTTP-запроса пользователя) заблокируется на минуты. AbortSignal.timeout(мс) прерывает запрос по истечении времени — ошибка попадает в catch. Это первое, что добавляют к серверным запросам во внешний мир.
Повторы при сбое
async function fetchWithRetry(url, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
if (response.ok) return response.json();
if (response.status < 500) break; // 4xx повторять бессмысленно
} catch (error) {
if (attempt === retries) throw error; // последняя попытка — пробрасываем
}
await new Promise(r => setTimeout(r, attempt * 1000)); // пауза растёт
}
throw new Error('Все попытки исчерпаны');
}
Внешние API временно падают — повтор часто спасает. Приём: несколько попыток с растущей паузой (экспоненциальная задержка). Тонкость: повторять стоит только сетевые сбои и 5xx (проблема на стороне сервера), а 4xx (неверный запрос, нет прав) повторять бессмысленно — ответ не изменится. Это база устойчивого взаимодействия между сервисами.
Чем серверный fetch отличается от браузерного
В браузере
Ограничен политиками
- Действует CORS — чужие хосты закрыты
- Куки и заголовки под контролем браузера
- Пользователь рядом, ждёт ответа
- Один запрос на действие
На сервере
Свободнее, но ответственнее
- Нет CORS — ходим куда угодно
- Любые заголовки, свои токены
- Таймауты и повторы обязательны
- Секреты в переменных окружения, не в коде
API одинаковый, но контекст разный. На сервере нет CORS и куки браузера — можно свободно обращаться к любым хостам и задавать любые заголовки, включая секретные токены. Но и ответственность выше: внешние API подводят, поэтому таймауты, повторы и обработка ошибок здесь не опция, а необходимость. И ключи API держат в переменных окружения, а не в коде.
Нужен ли ещё axios
Честный ответ: для простых запросов — нет. Встроенный fetch покрывает запросы к API, JSON, заголовки, таймауты. axios даёт удобства — автоматический парсинг JSON, перехватчики (interceptors), встроенные таймауты и повторы, — но это зависимость. Правило то же, что и везде: инструмент под задачу. Пара запросов к API — fetch. Сложная интеграция с единой обработкой всех запросов — можно и axios.
Смежные темы: fetch в браузере — тот же API на клиенте; JSON — формат данных; переменные окружения — где хранить токены API; обработка ошибок. Полный список — в уроках Node.js.