node-fetch の使い方

A light-weight module that brings Fetch API to node.js

v3.3.2127.1M/週MITHTTPクライアント
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

node-fetch の使い方 — Node.js で Fetch API を使う軽量 HTTP クライアント

一言でいうと

node-fetch は、ブラウザ標準の Fetch API を Node.js 環境で使えるようにする軽量モジュールです。XMLHttpRequest や重厚な HTTP クライアントに頼らず、fetch() のシンプルな構文でHTTPリクエストを実行できます。


どんな時に使う?

  1. 外部 API からデータを取得したい時 — REST API や GraphQL エンドポイントへのリクエストを、ブラウザと同じ fetch() 構文で書きたい場合
  2. ブラウザとサーバーでコードを共有したい時 — isomorphic(ユニバーサル)なコードベースで、HTTP リクエスト部分の実装を統一したい場合
  3. 軽量な 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-fetchaxiosgotundici (Native fetch)
API スタイルFetch API 準拠独自(axios 固有)独自(got 固有)Fetch API 準拠
バンドルサイズ軽量(~12KB)中程度(~40KB)やや大きい(~50KB+)Node.js 組み込み
ESM / CJSv3: 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 を導入するのがよいでしょう。