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 отличается от браузерного

fetch: браузер против сервера

В браузере

Ограничен политиками

  • Действует CORS — чужие хосты закрыты
  • Куки и заголовки под контролем браузера
  • Пользователь рядом, ждёт ответа
  • Один запрос на действие

На сервере

Свободнее, но ответственнее

  • Нет CORS — ходим куда угодно
  • Любые заголовки, свои токены
  • Таймауты и повторы обязательны
  • Секреты в переменных окружения, не в коде

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

Нужен ли ещё axios

Честный ответ: для простых запросов — нет. Встроенный fetch покрывает запросы к API, JSON, заголовки, таймауты. axios даёт удобства — автоматический парсинг JSON, перехватчики (interceptors), встроенные таймауты и повторы, — но это зависимость. Правило то же, что и везде: инструмент под задачу. Пара запросов к API — fetch. Сложная интеграция с единой обработкой всех запросов — можно и axios.


Смежные темы: fetch в браузере — тот же API на клиенте; JSON — формат данных; переменные окружения — где хранить токены API; обработка ошибок. Полный список — в уроках Node.js.

Частые вопросы

Есть ли fetch в Node.js?
Да, начиная с Node 18 fetch встроен глобально, устанавливать ничего не нужно. Это тот же fetch, что в браузере, поэтому код запросов к API выглядит одинаково на клиенте и сервере.
Нужен ли ещё axios в Node.js?
Для большинства задач нет — встроенный fetch покрывает запросы к API, отправку JSON и обработку ответов. axios остаётся полезен ради удобств вроде автоматического парсинга JSON, перехватчиков и таймаутов из коробки, но новую зависимость ради простых запросов тянуть не обязательно.
Как обработать ошибку запроса в Node?
Обернуть fetch в try/catch — он ловит сетевые сбои. HTTP-статусы 404 и 500 fetch не считает ошибкой, поэтому их проверяют вручную через response.ok перед разбором тела ответа.
Как сделать таймаут для fetch?
Через AbortSignal.timeout(мс), переданный в опцию signal: fetch(url, { signal: AbortSignal.timeout(5000) }). По истечении времени запрос прервётся и попадёт в catch как ошибка отмены. Без таймаута зависший сервер заблокирует запрос надолго.
Чем fetch на сервере отличается от браузерного?
API одинаковый, но на сервере нет ограничений CORS и куки браузера, зато можно свободно ходить к любым хостам и задавать любые заголовки. Также на сервере важнее таймауты и повторы, потому что внешние API могут подвести.