node-fetch の使い方 — Node.js で Fetch API を使う軽量 HTTP クライアント
一言でいうと
node-fetch は、ブラウザ標準の Fetch API を Node.js 環境で使えるようにする軽量モジュールです。XMLHttpRequest や重厚な HTTP クライアントに頼らず、fetch() のシンプルな構文でHTTPリクエストを実行できます。
どんな時に使う?
- 外部 API からデータを取得したい時 — REST API や GraphQL エンドポイントへのリクエストを、ブラウザと同じ
fetch()構文で書きたい場合 - ブラウザとサーバーでコードを共有したい時 — isomorphic(ユニバーサル)なコードベースで、HTTP リクエスト部分の実装を統一したい場合
- 軽量な HTTP クライアントが欲しい時 — axios や got ほどの機能は不要で、Promise ベースのシンプルなリクエストだけで十分な場合
補足: Node.js v18 以降にはネイティブの
fetch()が組み込まれています(undiciベース)。Node.js v18 未満をサポートする必要がある場合や、WHATWG Fetch 仕様への準拠度を重視する場合に node-fetch が選択肢になります。
インストール
# npm
npm install node-fetch
# yarn
yarn add node-fetch
# pnpm
pnpm add node-fetch
⚠️ v3 は ESM 専用です。 node-fetch v3.x は CommonJS (
require) をサポートしていません。CommonJS プロジェクトで使いたい場合は v2 系(npm install node-fetch@2)を利用してください。
基本的な使い方
// ESM (package.json に "type": "module" が必要)
import fetch from 'node-fetch';
// JSON を取得する最もシンプルなパターン
async function getUser(id: number) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
const user = await response.json() as { id: number; name: string; email: string };
console.log(user.name); // "Leanne Graham"
return user;
}
getUser(1);
TypeScript で型定義を利用する場合は、@types/node-fetch を追加でインストールしてください(v2 系の場合)。v3 系は TypeScript の型定義が同梱されています。
よく使う API
1. GET リクエスト — JSON の取得
import fetch from 'node-fetch';
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
async function getPosts(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = (await res.json()) as Post[];
return posts;
}
2. POST リクエスト — JSON の送信
import fetch from 'node-fetch';
async function createPost(title: string, body: string) {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, body, userId: 1 }),
});
if (!res.ok) {
throw new Error(`Failed to create post: ${res.status}`);
}
const created = await res.json();
console.log(created);
return created;
}
createPost('Hello', 'World');
3. レスポンスヘッダー・ステータスの確認
import fetch from 'node-fetch';
async function inspectResponse() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
console.log(res.ok); // true (status 200-299)
console.log(res.status); // 200
console.log(res.statusText); // "OK"
console.log(res.headers.get('content-type')); // "application/json; charset=utf-8"
console.log(res.url); // リダイレクト後の最終 URL
console.log(res.redirected); // リダイレクトされたかどうか
}
inspectResponse();
4. ストリームとしてレスポンスを処理する
import fetch from 'node-fetch';
import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
async function downloadFile(url: string, dest: string) {
const res = await fetch(url);
if (!res.ok || !res.body) {
throw new Error(`Download failed: ${res.status}`);
}
// res.body は Node.js の Readable Stream として扱える
await pipeline(res.body, createWriteStream(dest));
console.log(`Downloaded to ${dest}`);
}
downloadFile('https://via.placeholder.com/150', './image.png');
5. リクエストのタイムアウトと AbortController
import fetch, { AbortError } from 'node-fetch';
async function fetchWithTimeout(url: string, timeoutMs: number) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} catch (err) {
if (err instanceof AbortError) {
console.error(`Request timed out after ${timeoutMs}ms`);
}
throw err;
} finally {
clearTimeout(timer);
}
}
fetchWithTimeout('https://jsonplaceholder.typicode.com/posts', 5000);
類似パッケージとの比較
| 特徴 | node-fetch | axios | got | undici (Native fetch) |
|---|---|---|---|---|
| API スタイル | Fetch API 準拠 | 独自(axios 固有) | 独自(got 固有) | Fetch API 準拠 |
| バンドルサイズ | 軽量(~12KB) | 中程度(~40KB) | やや大きい(~50KB+) | Node.js 組み込み |
| ESM / CJS | v3: ESM のみ / v2: CJS | 両対応 | ESM のみ (v12+) | Node.js 組み込み |
| ストリーム対応 | ✅ Node.js Stream | ✅ | ✅ | ✅ Web Stream |
| リトライ機能 | ❌ 自前実装が必要 | ❌ 自前実装が必要 | ✅ 組み込み | ❌ 自前実装が必要 |
| インターセプター | ❌ | ✅ | ✅ (hooks) | ❌ |
| Node.js 最低バージョン | v12.20+ (v3) | v12+ | v14+ | v18+ (実験的 v16.15+) |
選び方の目安:
- Node.js v18 以上のみ対象 → ネイティブ
fetchで十分なことが多い - ブラウザ互換の Fetch API が欲しい + v18 未満もサポート → node-fetch
- リトライ・インターセプター等の高機能が必要 → axios / got
注意点・Tips
1. v3 は ESM 専用
// package.json に以下が必要
{
"type": "module"
}
CommonJS プロジェクトで v3 を使おうとすると ERR_REQUIRE_ESM エラーが発生します。CommonJS が必須なら node-fetch@2 を使ってください。
2. response.json() は一度しか呼べない
const res = await fetch(url);
const data1 = await res.json();
// const data2 = await res.json(); // ❌ Error: body used already
レスポンスボディは一度消費すると再読み取りできません。複数回使いたい場合は変数に保存してください。
3. エラーハンドリングの落とし穴
fetch() は HTTP 4xx/5xx でも reject しません。ネットワークエラーの場合のみ例外がスローされます。必ず response.ok または response.status をチェックしてください。
const res = await fetch(url);
// ❌ 404 でもここに到達する
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
4. リダイレクトの制御
// リダイレクトを自動で追従しない
const res = await fetch(url, { redirect: 'manual' });
console.log(res.status); // 301, 302 など
console.log(res.headers.get('location')); // リダイレクト先 URL
// 'error' を指定するとリダイレクト時に例外をスロー
// 'follow' がデフォルト(最大20回まで自動追従)
5. カスタムヘッダーと認証
import fetch, { Headers } from 'node-fetch';
const headers = new Headers({
'Authorization': `Bearer ${process.env.API_TOKEN}`,
'Accept': 'application/json',
});
const res = await fetch('https://api.example.com/data', { headers });
まとめ
node-fetch は、ブラウザの Fetch API を Node.js に持ち込む軽量で信頼性の高いライブラリです。v3 系は ESM 専用になった点に注意が必要ですが、シンプルな HTTP リクエストには最適な選択肢です。ただし、Node.js v18 以降ではネイティブ fetch が利用可能なため、新規プロジェクトではまずネイティブ実装を検討し、それで不足がある場合に node-fetch を導入するのがよいでしょう。