undici の使い方 — Node.js のための高性能 HTTP/1.1 クライアント
一言でいうと
undici は、Node.js のためにゼロから書き直された高性能な HTTP/1.1 クライアントです。Node.js v18+ に組み込まれている fetch() の内部エンジンでもあり、単体モジュールとしてインストールすることで、request・stream・pipeline などの高速APIやコネクションプーリングの細かな制御が可能になります。
どんな時に使う?
- 高パフォーマンスが求められる API 通信 —
undici.request()は標準のfetch()やaxiosと比較して数倍のスループットを発揮します。マイクロサービス間通信やバッチ処理など、大量のHTTPリクエストを捌く場面に最適です。 - プロキシ経由・モック付きのHTTP通信 —
ProxyAgent、Socks5Agent、MockAgentなど、組み込みfetchでは利用できない高度な機能が必要な場合に使います。 - テスト環境でのHTTPモック —
MockAgentを使えば、外部ライブラリなしでHTTPリクエストをインターセプト・モックでき、テストの信頼性と速度が向上します。
インストール
# npm
npm install undici
# yarn
yarn add undici
# pnpm
pnpm add undici
Note: Node.js v18+ にはundiciベースの
fetch()が組み込まれていますが、モジュールとして別途インストールすることで最新機能・最新パフォーマンス改善を利用できます。
基本的な使い方
最もよく使うパターンは request() による高速なHTTPリクエストです。
import { request } from 'undici';
// GET リクエスト
async function fetchUser(userId: string) {
const { statusCode, headers, body } = await request(`https://api.example.com/users/${userId}`);
if (statusCode !== 200) {
// body は必ず消費する必要がある(後述の注意点参照)
await body.dump();
throw new Error(`Request failed with status ${statusCode}`);
}
const data = await body.json() as { id: string; name: string; email: string };
console.log(data);
return data;
}
// POST リクエスト(JSON)
async function createUser(name: string, email: string) {
const { statusCode, body } = await request('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, email }),
});
const result = await body.json();
return { statusCode, result };
}
よく使う API — undici の使い方を API 別に解説
1. request() — 最速の汎用リクエスト
undici で最もパフォーマンスが高い API です。レスポンスを statusCode・headers・body に分解して返します。
import { request } from 'undici';
// クエリパラメータ付き GET
const { statusCode, headers, body } = await request('https://api.example.com/search', {
method: 'GET',
query: {
q: 'undici',
page: '1',
},
headers: {
Authorization: 'Bearer my-token',
},
headersTimeout: 5000, // ヘッダー受信までのタイムアウト(ms)
bodyTimeout: 30000, // ボディ受信までのタイムアウト(ms)
});
const text = await body.text();
console.log(`Status: ${statusCode}`);
console.log(`Content-Type: ${headers['content-type']}`);
console.log(text);
2. fetch() — Web 標準互換の Fetch API
ブラウザの fetch() と同じインターフェースです。組み込み fetch のドロップイン置き換えとして使えます。
import { fetch, Headers } from 'undici';
const headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
});
const response = await fetch('https://api.example.com/data', {
method: 'POST',
headers,
body: JSON.stringify({ key: 'value' }),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
console.log(data);
3. Agent / setGlobalDispatcher() — コネクションプーリングの制御
コネクションの再利用やタイムアウトをきめ細かく設定できます。
import { Agent, setGlobalDispatcher, request } from 'undici';
// カスタム Agent を作成
const agent = new Agent({
keepAliveTimeout: 10_000, // Keep-Alive タイムアウト(ms)
keepAliveMaxTimeout: 30_000, // Keep-Alive 最大タイムアウト(ms)
connections: 100, // オリジンあたりの最大コネクション数
pipelining: 10, // パイプライニング深度
});
// グローバルに適用(以降の request / fetch すべてに影響)
setGlobalDispatcher(agent);
// または個別リクエストで dispatcher を指定
const { body } = await request('https://api.example.com/data', {
dispatcher: agent,
});
const data = await body.json();
4. MockAgent — テスト用 HTTP モック
外部サービスへのリクエストをインターセプトしてモックレスポンスを返します。
import { MockAgent, setGlobalDispatcher, request } from 'undici';
// MockAgent を作成してグローバルに設定
const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);
// 実際のネットワーク接続を無効化
mockAgent.disableNetConnect();
// モックプールを取得し、インターセプトを設定
const mockPool = mockAgent.get('https://api.example.com');
mockPool.intercept({
path: '/users/1',
method: 'GET',
}).reply(200, {
id: '1',
name: 'Test User',
email: 'test@example.com',
}, {
headers: { 'Content-Type': 'application/json' },
});
// モックされたレスポンスが返る
const { statusCode, body } = await request('https://api.example.com/users/1');
const data = await body.json();
console.log(statusCode); // 200
console.log(data); // { id: '1', name: 'Test User', email: 'test@example.com' }
// テスト後のクリーンアップ
await mockAgent.close();
5. ProxyAgent — プロキシ経由のリクエスト
HTTP/HTTPS プロキシを経由してリクエストを送信します。
import { ProxyAgent, setGlobalDispatcher, request } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
setGlobalDispatcher(proxyAgent);
// 以降のリクエストはすべてプロキシ経由
const { statusCode, body } = await request('https://api.example.com/data');
const data = await body.json();
console.log(statusCode, data);
// 個別リクエストでプロキシを指定することも可能
const { body: body2 } = await request('https://other-api.example.com', {
dispatcher: proxyAgent,
});
類似パッケージとの比較
| 特徴 | undici | axios | node-fetch | got |
|---|---|---|---|---|
| Node.js 公式 | ✅(Node.js コアに組み込み) | ❌ | ❌ | ❌ |
| パフォーマンス | ⚡ 非常に高速 | 普通 | 普通 | やや高速 |
| HTTP/1.1 パイプライニング | ✅ | ❌ | ❌ | ❌ |
| Fetch API 互換 | ✅ | ❌ | ✅ | ❌ |
| 組み込みモック | ✅ MockAgent | ❌(別途 mock ライブラリ必要) | ❌ | ❌ |
| プロキシサポート | ✅ ProxyAgent | ✅ | ❌(別途必要) | ✅ |
| ブラウザ対応 | ❌(Node.js 専用) | ✅ | ❌ | ❌ |
| リトライ機能 | インターセプターで実装 | ❌(別途必要) | ❌ | ✅ 組み込み |
| TypeScript 型定義 | ✅ 同梱 | ✅ 同梱 | ✅(@types) | ✅ 同梱 |
| 依存パッケージ数 | 0 | 数個 | 数個 | 多め |
注意点・Tips
1. レスポンスボディは必ず消費する
request() で取得したボディを読まずに放置すると、コネクションがリークします。エラー時でも必ずボディを消費してください。
import { request } from 'undici';
const { statusCode, body } = await request('https://api.example.com/data');
if (statusCode !== 200) {
// ボディを読み捨てる(これを忘れるとコネクションリーク)
await body.dump();
throw new Error(`Unexpected status: ${statusCode}`);
}
const data = await body.json();
2. fetch と FormData は同じ実装から揃える
undici の fetch とグローバルの FormData を混在させると、互換性の問題が発生します。必ず同じ出所から import してください。
// ✅ 正しい — 両方 undici から import
import { fetch, FormData } from 'undici';
const form = new FormData();
form.set('file', new Blob(['hello']), 'hello.txt');
await fetch('https://example.com/upload', { method: 'POST', body: form });
// ❌ 間違い — グローバル FormData と undici fetch を混在
// import { fetch } from 'undici';
// const form = new FormData(); // ← グローバルの FormData
// await fetch('...', { body: form }); // 動作が不安定になる可能性
3. request() と fetch() の使い分け
- パフォーマンス最優先 →
request()を使う(ベンチマークでfetch()の約3倍速い場合も) - Web 標準互換・ポータビリティ重視 →
fetch()を使う - ストリーム処理 →
stream()を使う
4. タイムアウトの設定
デフォルトではタイムアウトが長めに設定されています。本番環境では明示的に設定しましょう。
import { request } from 'undici';
const { body } = await request('https://api.example.com/data', {
headersTimeout: 5_000, // 5秒以内にヘッダーが届かなければエラー
bodyTimeout: 30_000, // 30秒以内にボディ受信が完了しなければエラー
signal: AbortSignal.timeout(60_000), // 全体のタイムアウト
});
5. 組み込み fetch との共存
process.versions.undici でNode.jsにバンドルされているundiciのバージョンを確認できます。モジュール版をインストールすると、バンドル版より新しい機能を使えます。
// Node.js にバンドルされている undici のバージョンを確認
console.log(process.versions.undici); // 例: "6.21.1"
6. install() でグローバルを置き換える
undici モジュールの実装でグローバルの fetch / FormData 等を一括置換できます。
import { install } from 'undici';
// グローバルの fetch, Headers, Request, Response, FormData を undici 版に置換
install();
// 以降はグローバル fetch が undici の最新版になる
const res = await fetch('https://api.example.com/data');
まとめ
undici は Node.js 公式の HTTP クライアントエンジンであり、request() API を使えば axios や node-fetch を大幅に上回るパフォーマンスを発揮します。MockAgent によるテストモック、ProxyAgent によるプロキシ対応、コネクションプーリングの細かな制御など、本番運用に必要な機能が依存ゼロで揃っています。Node.js でHTTP通信を行うなら、まず undici を検討すべき選択肢です。