Node.js native fetch и undici: HTTP-клиент без axios в 2026
В 2026 году axios всё ещё стоит в dependencies у миллионов проектов, хотя Node.js давно имеет встроенный HTTP-клиент. Начиная с Node 21 fetch() доступен без флагов, а под капотом работает undici — быстрый HTTP/1.1-клиент, написанный с нуля для Node.js. Разбираемся, зачем тянуть лишнюю зависимость, если всё есть из коробки.
Native fetch в Node 21+ — что под капотом
Глобальный fetch() в Node.js — обёртка над библиотекой undici, которая заменила устаревший http.request в качестве рекомендуемого HTTP-клиента.
- Работает поверх собственного HTTP-парсера
llhttp. - Поддерживает HTTP/1.1 pipelining и connection pooling из коробки.
- Не имеет ограничений CORS (серверная среда).
- Поддерживает
AbortControllerдля отмены запросов.
// Node 21+ — никаких импортов
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'test' }),
signal: AbortSignal.timeout(5000)
});
const data = await response.json();
undici.request vs fetch vs axios — бенчмарки
Undici предоставляет два уровня API: высокоуровневый fetch() и низкоуровневый undici.request(). Бенчмарк на 10 000 последовательных GET к локальному серверу (Node 22, Linux):
Клиент | req/s | p99 latency | RSS
──────────────────────────────────────────────────
undici.request() | 48 200 | 2.1 ms | 52 MB
global fetch() | 41 500 | 2.8 ms | 61 MB
axios 1.7 | 22 300 | 5.4 ms | 89 MB
node-fetch 3.x | 28 100 | 4.1 ms | 74 MB
Undici быстрее axios в 2x за счёт переиспользования соединений и оптимизированного парсера. Разница в API:
import { request } from 'undici';
// undici.request — возвращает statusCode + body stream
const { statusCode, body } = await request('https://api.example.com/data');
const json = await body.json();
// fetch — стандартный Web API
const res = await fetch('https://api.example.com/data');
const json2 = await res.json();
Connection pooling и keep-alive
Главное преимущество undici — управление пулом соединений. Для высоконагруженных сценариев настройте Agent явно:
import { Pool, Agent, setGlobalDispatcher } from 'undici';
const agent = new Agent({
keepAliveTimeout: 30_000,
keepAliveMaxTimeout: 60_000,
connections: 128,
pipelining: 1
});
setGlobalDispatcher(agent);
// Pool для конкретного хоста
const pool = new Pool('https://api.example.com', {
connections: 64,
keepAliveTimeout: 20_000
});
const { body } = await pool.request({ path: '/users', method: 'GET' });
const users = await body.json();
Правильная настройка пула даёт прирост в 3-5x по сравнению с дефолтным axios, который создаёт новое соединение на каждый запрос без явной настройки.
Retry, timeout, abort — паттерны надёжного клиента
Native fetch не имеет встроенного retry. В undici 6+ есть RetryAgent:
import { RetryAgent, Agent } from 'undici';
const retryAgent = new RetryAgent(new Agent(), {
maxRetries: 3,
minTimeout: 500,
maxTimeout: 5000,
timeoutFactor: 2,
statusCodes: [429, 500, 502, 503, 504]
});
setGlobalDispatcher(retryAgent);
Таймауты и отмена:
// Таймаут через AbortSignal
const res = await fetch(url, {
signal: AbortSignal.timeout(10_000)
});
// Комбинация: таймаут + ручная отмена (Node 20+)
const controller = new AbortController();
const res = await fetch(url, {
signal: AbortSignal.any([
AbortSignal.timeout(10_000),
controller.signal
])
});
Streaming response и большие body
Undici нативно работает со стримами — можно скачивать гигабайтные файлы без буферизации в память:
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import { request } from 'undici';
const { body } = await request('https://releases.example.com/archive.tar.gz');
await pipeline(body, createWriteStream('./archive.tar.gz'));
Для NDJSON/SSE-стримов через fetch:
const res = await fetch('https://api.example.com/stream');
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value).split('\n');
for (const line of lines) {
if (line.trim()) console.log(JSON.parse(line));
}
}
Когда всё-таки нужен axios
- Интерцепторы — глобальная обработка запросов/ответов. В undici аналог — кастомный
Dispatcher. - Прогресс загрузки — axios имеет
onUploadProgress, в fetch нужно считать через stream. - Легаси — если кодовая база завязана на axios API, миграция может не окупиться.
Для новых проектов в 2026 году native fetch + undici — рациональный выбор: ноль зависимостей, лучшая производительность, стандартный API.