Node.js の fetch() は、基本的な HTTP リクエストなら短いコードで実装できます。一方、本番アプリでは、4xx・5xxの判定、応答待ちの上限、再試行、接続の再利用まで設計しないと、障害の検知が遅れたり、処理が長時間止まったりすることがあります。
私はこれまで Thunderbit で社内ツールやデータパイプラインをいろいろ作ってきました。その経験から、「チュートリアルで fetch が動く」ことと「本番環境で安定して運用できる」ことの間には、いくつかの実装上の差があると感じています。Reddit のある開発者も、「本番に入って初めて、ネイティブの fetch だけでは不十分だと気づく」 と述べています。
別の開発者は、「Web 開発者として 3 年働いてきたけど、今日初めて知った。fetch API の catch ブロックは HTTP エラー用じゃない」 と書いています。本ガイドでは、Node.jsで外部API、バックエンド処理、自動化スクリプトを実装する開発者に向けて、多くの入門チュートリアルが省きやすい5項目を扱います。具体的には、HTTPエラーの判定、AbortControllerによるタイムアウト、リトライロジック、接続の再利用、そして構造化データ抽出に別の手段を検討する判断基準です。

Node.js Fetch API とは?
Node.js Fetch API は、Node.js から HTTP リクエスト(GET、POST、PUT、DELETE など)を送るための、ブラウザ互換の標準機能です。Axios や node-fetch、その他のパッケージをインストールする必要はありません。ブラウザで fetch() を使ったことがあれば、文法はすでにおなじみでしょう。同じ API がサーバー側でも使えるようになった、ということです。
まずは簡単なバージョンの変遷を見てみましょう。
| マイルストーン | Node バージョン | 何が起きたか |
|---|---|---|
| 実験的 fetch フラグ | v17.5.0 / v16.15.0 | --experimental-fetch の下で fetch が追加 |
| デフォルトのグローバル fetch | v18.0.0 | 実験的 fetch がグローバルで利用可能に。 Undici が基盤 |
| 安定版 fetch | v21.0.0 | 実験段階を終了 |
| 2026 年の本番基準 | v22 LTS / v24 LTS | 本番利用に推奨。v20 は現在 EOL |
内部では、Node の fetch は Undici によって動いています。Undici は Node.js 向けに作られた高性能 HTTP クライアントです。古い組み込みの http モジュールには依存していません。実用上のメリットは、ブラウザコード、Express バックエンド、サーバーレス関数、CLI スクリプトで同じように使える、モダンな Promise ベースの HTTP API を得られることです。
Node.js Fetch API がプロジェクトで重要な理由
Node 18 より前は、新しいプロジェクトを始めるたびに npm install axios か npm install node-fetch を実行するケースが一般的でした。2026 年の現在、メンテナンスされている Node LTS 上で基本的な HTTP リクエストを実装するだけなら、追加の依存関係は必須ではありません。バンドルサイズ、サプライチェーン管理、オンボーディングに加え、フロントエンドとバックエンドで同じ API を使える点も利点です。
ネイティブ fetch が特に強い場面は次のとおりです。
| シナリオ | ネイティブ fetch が向いている理由 | 本番での注意点 |
|---|---|---|
| REST API を呼び出す Express/Fastify バックエンド | おなじみの async/await、依存関係なし | タイムアウトと response.ok の確認を追加する |
| サーバーレス関数(Lambda、Vercel など) | コールドスタートの負担が小さく、パッケージ導入不要 | タイムアウトはプラットフォーム上限より短くする |
| CLI スクリプトや自動化処理 | セットアップなしでシンプルに GET/POST できる | 不安定な API にはリトライ/バックオフを追加する |
| Webhook の送信や中継 | 標準の HTTP メソッドとヘッダーが使える | 冪等でない POST を安易にリトライしない |
| レポートやダッシュボード | API から JSON を取ってくる用途に向く | ループ処理ではページネーションと接続プーリングを使う |
| マイクロサービス間通信 | シンプルな内部 HTTP 呼び出しに適する | リトライ、フック、HTTP/2 が必要なら Got や Undici を直接検討する |
新しい Node 22+ プロジェクトでは、ネイティブ fetch は基本的な選択肢になります。ただし、インターセプター、組み込みリトライ、HTTP/2 など、要件に含まれる機能が不足する場合は別のクライアントも比較します。npm では、node-fetch は今でも週約 1 億 6920 万回 ダウンロードされていますが、その多くはレガシーやトランジティブ依存です。Axios は約 1 億 1270 万、Undici は約 1 億 3190 万、Got は約 3640 万、Ky は約 660 万 です。ダウンロード数だけで適否は決められないため、実行環境と必要な機能を基準に選ぶことが重要です。
ネイティブ Fetch vs node-fetch vs Axios vs Got vs Ky:2026 年版の判断マトリクス
Node.jsのHTTPクライアントは、標準機能を優先するか、追加機能をパッケージ側に持たせるかで選択が変わります。ネイティブ機能があるなら追加ライブラリは不要、という考え方もありますが、実際には実行環境と必要な機能を整理して判断する必要があります。

| 機能 | ネイティブ fetch | node-fetch v3 | axios | got v15 | ky v2 |
|---|---|---|---|---|---|
| Node.js バージョン | ≥18(推奨は 22/24 LTS) | ≥12.20 | 幅広い | ≥22 | ≥22 |
| インストール不要か | はい | いいえ | いいえ | いいえ | いいえ |
| ESM + CJS 対応 | 両方(グローバル) | ESM のみ(v3) | 両方 | ESM のみ | ESM のみ |
| 4xx/5xx で自動 reject | いいえ | いいえ | はい | はい | はい |
| 組み込みリトライ | いいえ | いいえ | いいえ | はい | はい |
| リクエストインターセプター | いいえ | いいえ | はい | はい(hooks) | はい(hooks) |
| ストリーミング対応 | Web ReadableStream | はい | 制限あり | 強力な Node streams | fetch ベース |
| バンドル/導入サイズ | 0 KB | 約107 KB、依存 3 個 | 約2.8 MB、依存 4 個 | 約355 KB、依存 12 個 | 約405 KB、依存 0 個 |
| HTTP/2 対応 | Undici dispatcher 経由 | いいえ | いいえ | はい | いいえ(fetch ラッパー) |
ESM/CJSの扱いも判断材料です。node-fetch v3はESMのみであるため、require()を使うプロジェクトでは移行対応が必要です。ネイティブfetchはグローバルAPIとして利用できるため、CJSとESMのどちらでもimportの追加調整を減らせます。CommonJSとの互換性を理由にnode-fetch v2を使い続けている場合は、ネイティブfetchへの移行を比較候補にできます。
初期の安定性については、Node 18初期のfetch実装で不具合を経験したという報告があります。Reddit のある開発者は、「最近、ネイティブの Node 18 fetch でひどいバグに遭遇して、アプリを移行せざるを得なかった」 と書いています。それは 2023 年の話です。2026 年にNode 22や24 LTSを採用する場合は、当時の報告だけで判断せず、対象バージョンのリリースノート、既知の問題、必要な機能を照合したうえで本番採用を決めます。
ネイティブ Fetch を使い続けるべき場面
次のような場合はネイティブ fetch を選びましょう。
- プロジェクトが Node 22 LTS か Node 24 LTS で動作している。
- リクエストがシンプルな REST 呼び出し(GET、POST、PUT、DELETE)である。
response.ok、JSON 解析、タイムアウト、リトライを少しラップする程度なら許容できる。- 依存関係をゼロにして、サプライチェーンの懸念を減らしたい。
- ブラウザとサーバーで API を揃えたい。
- サーバーレスやエッジ環境で、組み込み API を優先したい。
Axios、Got、Ky の方が向いている場面
Axios は、リクエスト/レスポンスのインターセプターにチームが強く依存している場合(例:認証トークンの自動更新、テナントヘッダー、中央集約ログ)、HTTP エラー時の自動 reject が欲しい場合、あるいは古い Node 実行環境との互換性が必要な場合に向いています。
Got は、高スループットのNodeサービスで、組み込みリトライ、hooks、高度なタイムアウト段階、ストリーム、ページネーション補助、Unixソケット、プロキシ/キャッシュのワークフロー、HTTP/2対応が必要な場合の候補です。Node専用のHTTP処理を細かく制御したいケースに向いています。
Ky は、fetchに近いAPIを維持しながら記述量を減らしたい場合に向いています。リトライ、タイムアウト、hooks、HTTPErrorを、依存関係ゼロの小さなパッケージで追加できます。
Node.js Fetch API で GET リクエストを送る方法
async/awaitを使ったGETリクエストは、次のように記述します。
const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response.json();
console.log(post.title);
// → "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
.then()のチェーンで記述する場合は、次の形になります。
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(response => response.json())
.then(post => console.log(post.title))
.catch(error => console.error(error));
どちらも基本的な取得処理としては動作しますが、本番運用にはHTTPステータスの判定などを追加する必要があります。
responseの主な読み取り方法:
| メソッド | 使う場面 |
|---|---|
response.json() | サーバーが JSON を返す |
response.text() | HTML、プレーンテキスト、CSV、Markdown を返す |
response.arrayBuffer() | バイナリデータ(画像、ファイル)が必要 |
response.body | ストリーミング/チャンク処理が必要 |
HTTPエラーを判定する基本形は次のとおりです。
async function getPost(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
return response.json();
}
const post = await getPost(1);
console.log(post.title);
この if (!response.ok) は、本番コードでHTTPエラーを見落とさないための基本条件です。次のセクションでは、fetchのエラー処理が通常の例外処理と異なる点を整理します。
Node.js Fetch API で POST リクエストを送る方法
POST リクエストも形は同じで、method、headers、body を設定するだけです。
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Node fetch ガイド',
body: '本番の fetch にはエラーハンドリングが必要です。',
userId: 1,
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const created = await response.json();
console.log(created.id); // → 101
その他のリクエスト種別(PUT、DELETE、PATCH)を送る
PUT、PATCH、DELETE も、method の値が違うだけで構造は同じです。
// PUT — 全体置き換え
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 1, title: '置き換え後', body: '完全置換', userId: 1 }),
});
// PATCH — 部分更新
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: '部分更新' }),
});
// DELETE
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
method: 'DELETE',
});
Expressでreq.bodyがundefinedになる場合: ExpressサーバーへJSONをPOSTしてもreq.bodyを取得できない場合は、JSON本文を解析するミドルウェアがルートより前に登録されているかを確認します。Content-Type: application/jsonの本文には、express.urlencoded()ではなくexpress.json()を使用します。この問題は、Expressに関するStack Overflow で最もよくある質問の一つでも扱われています。
import express from 'express';
const app = express();
app.use(express.json()); // ← JSON POST 本文に必要なのはこちら
app.post('/api/posts', (req, res) => {
res.json({ received: req.body });
});
本番環境で注意すべきfetch()のエラー処理

fetchを本番利用する際は、ネットワーク障害とHTTPエラーを分けて処理する必要があります。
fetch() は、HTTP 4xx や 5xx では promise を reject しません。 reject するのはネットワークレベルの失敗だけです。DNS エラー、インターネット未接続、リクエストの中断などです。サーバーが 403 Forbidden や 500 Internal Server Error を返しても、fetchはレスポンスを受け取ったものとして処理を続けます。.catch()は呼ばれず、try/catchだけでもHTTPエラーは捕捉できません。
MDN のドキュメントにもこの挙動が記載されています。したがって、次のコードでは通信自体が成立すると、HTTPステータスがエラーでもレスポンス本文の解析へ進みます。
try {
const response = await fetch('https://api.example.com/private');
const data = await response.json(); // ← 403 でもここは実行される
console.log('成功したように見える:', data);
} catch (error) {
// ここで捕まるのはネットワークレベルの失敗だけ
console.error('捕捉:', error);
}
各パターンが捕捉する範囲は、次のとおりです。
| パターン | ネットワークエラーを捕捉 | 4xx/5xx を捕捉 | JSON を安全に解析 | 再利用性 |
|---|---|---|---|---|
生の .then(res => res.json()) | はい(.catch() 経由) | いいえ | content-type の保護なし | いいえ |
await fetch() を使う try/catch | はい | いいえ | content-type の保護なし | いいえ |
各呼び出しで手動 if (!res.ok) | はい | はい | 各呼び出し次第 | 一部 |
カスタム fetchJSON() ラッパー | はい | はい | はい | はい |
再利用できる fetchJSON() ラッパーを作る
各呼び出しでif (!response.ok)を重複させないように、HTTPステータス、レスポンス形式、エラー情報を共通処理するラッパーを用意します。
export class HTTPError extends Error {
constructor(message, { status, statusText, url, body }) {
super(message);
this.name = 'HTTPError';
this.status = status;
this.statusText = statusText;
this.url = url;
this.body = body;
}
}
export async function fetchJSON(url, options = {}) {
const response = await fetch(url, {
headers: {
Accept: 'application/json',
...options.headers,
},
...options,
});
const contentType = response.headers.get('content-type') || '';
const isJSON = contentType.includes('application/json');
const body = isJSON ? await response.json().catch(() => null) : await response.text();
if (!response.ok) {
throw new HTTPError(`HTTP ${response.status} ${response.statusText}`, {
status: response.status,
statusText: response.statusText,
url: response.url,
body,
});
}
return body;
}
サーバーが403を返した場合は、次のようにHTTPエラーとその他の失敗を分けて処理できます。
try {
const data = await fetchJSON('https://api.example.com/private');
} catch (error) {
if (error instanceof HTTPError) {
console.error(`サーバーが ${error.status} を返しました:`, error.body);
} else {
console.error('ネットワークまたはその他の失敗:', error);
}
}
このラッパーでは、ステータスコード、レスポンス本文、URLをエラーに含めます。ログ、アラート、ユーザー向けメッセージで必要な情報を同じ形式で扱えるため、共通モジュールとして再利用できます。
AbortController とタイムアウト:Node.js Fetch API の本番パターン

アプリケーション側で応答期限を設けないと、相手サーバーの応答が遅い場合にfetchの待機時間が長引きます。その間、Expressのルート、Lambdaの実行時間、自動化スクリプトの後続処理にも影響します。
Node.js向けの入門的なfetchチュートリアルでは、リクエストのキャンセルやタイムアウトが省かれることがあります。しかし本番運用では、上流APIと自システムの制限に合わせて待機時間を決める必要があります。Redditには、*「Node の fetch はタイムアウトしない」*というタイトルのスレッドもあります。
AbortSignal.timeout() を使う(Node 18.11+)
待機時間だけを指定する場合は、signalオプションにAbortSignal.timeout()を設定します。
try {
const response = await fetch('https://api.example.com/data', {
signal: AbortSignal.timeout(5000), // 5 秒
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('リクエストは 5 秒後にタイムアウトしました。');
} else {
throw error;
}
}
AbortSignal.timeout()による期限超過ではTimeoutError、AbortController.abort()による中断ではAbortErrorを判定します。
AbortController を使った手動タイムアウト
より細かく制御したい場合、あるいは単なるタイマーではなくユーザー操作に応じてキャンセルしたい場合は、こちらです。
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.error('リクエストは手動で中断されました。');
} else {
throw error;
}
} finally {
clearTimeout(timeout);
}
AbortError と TimeoutError の違いをどう扱うか
中断理由をログやユーザー向けメッセージに反映するには、エラー名を区別して処理します。
| 中断の経路 | catch ブロック内のエラー名 |
|---|---|
AbortSignal.timeout(ms) | TimeoutError |
controller.abort() | AbortError |
| DNS/ネットワーク障害 | 通常は TypeError: fetch failed |
次の例では、外部APIを呼び出すExpressルートに3秒の応答期限を設定し、期限を超えた場合に504を返します。
app.get('/dashboard', async (req, res, next) => {
try {
const data = await fetchJSON('https://api.example.com/report', {
signal: AbortSignal.timeout(3000),
});
res.json(data);
} catch (error) {
if (error.name === 'TimeoutError') {
res.status(504).json({ error: '上流 API がタイムアウトしました' });
return;
}
next(error);
}
});
この処理により、上流APIの遅延時に待機を打ち切る条件と、クライアントへ返す結果を明示できます。実際の期限は、上流APIの特性と自システムのタイムアウト上限に合わせて決めます。
リトライロジックと接続の再利用:Node.js Fetch API を本番品質にする
ネイティブfetchには、組み込みのリトライ機能がありません。一時的なネットワーク障害や503レスポンスが発生した場合、呼び出し側で再試行を実装しなければ、そのリクエストは失敗として終了します。読み取り処理で再試行が必要かどうかは、リクエストの冪等性、上流サービスの制限、許容できる待機時間を基準に判断します。
指数バックオフを使った再利用可能なリトライラッパー
次の例は、再試行対象のステータスと待機時間を示すため、処理を約10行にまとめています。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
export async function fetchWithRetry(url, options = {}, retries = 2) {
for (let attempt = 0; ; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok || ![408, 429, 500, 502, 503, 504].includes(response.status)) {
return response;
}
if (attempt >= retries) return response;
} catch (error) {
if (attempt >= retries) throw error;
}
await wait(250 * 2 ** attempt); // 250ms, 500ms, 1000ms...
}
}
いつリトライすべきか、いつすべきでないか
- リトライしてよい: 冪等な GET と HEAD、408 / 429 / 500 / 502 / 503 / 504 のような一時的ステータス、ネットワークの瞬断です。
- リトライしない: レコード作成、課金、その他副作用を起こす冪等でない POST。idempotency key を使う場合を除きます。
- Retry-After を尊重する: 429(レート制限)や 503(サービス利用不可)のレスポンスに
Retry-Afterヘッダーがある場合は、その値を待機時間に反映します。
自前でリトライロジックを管理したくない場合は、Kyも比較候補になります。fetchに近いAPIを維持しながら、リトライ、タイムアウト、hooks、HTTPErrorを利用できる軽量なラッパーで、依存関係はゼロです。
Undici の Agent と Pool で接続を再利用する
高スループットのループ、たとえば何百ページものスクレイピング、API の一括呼び出し、サービスのポーリングでは、TCP 接続を再利用するとかなり時間を節約できます。新しい接続を作るたびに、DNS ルックアップ、TCP ハンドシェイク、そして HTTPS では TLS ネゴシエーションが発生するからです。
Node の fetch は Undici で動いているので、カスタム dispatcher を渡せます。
import { Agent } from 'undici';
const agent = new Agent({
keepAliveTimeout: 10_000,
keepAliveMaxTimeout: 60_000,
});
const response = await fetch('https://api.example.com/data', {
dispatcher: agent,
});
特定の origin をもっと細かく制御したい場合は、こちらです。
import { Pool } from 'undici';
const pool = new Pool('https://api.example.com', { connections: 10 });
const response = await fetch('https://api.example.com/data', {
dispatcher: pool,
});
// 終わったら:
await pool.close();
Undici README のベンチマーク では、接続の再利用とプーリングでスループットが大きく向上することが示されています。ローカルベンチマークでは undici - dispatch が約 22,234 req/sec、undici - fetch が約 5,904 req/sec でした。実運用では数字は変わりますが、方向性は明確です。同じ origin に大量アクセスするなら、プーリングは重要です。
もう一つ大事なことがあります。レスポンス本文は必ず消費するかキャンセルしてください。消費されない本文は、Node の HTTP 内部でリソースリークの原因になります。
Node.js Fetch API でストリーミングレスポンスを扱う
大きなファイルのダウンロード、チャンク化された JSON フィード、Server-Sent Events、LLM の出力——これらは、全体のレスポンスを待ってから処理すると時間もメモリも無駄になります。ストリーミングなら、届いた順にデータを処理できます。

Node 18+ には、ブラウザ互換の ReadableStream が含まれています。改行区切り JSON のレスポンスをストリームし、届いた行ごとに処理する例はこちらです。
const response = await fetch('https://example.com/large-file.ndjson');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let newlineIndex;
while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
const line = buffer.slice(0, newlineIndex).trim();
buffer = buffer.slice(newlineIndex + 1);
if (line) {
const item = JSON.parse(line);
console.log('処理済み:', item.id);
}
}
}
もっと単純なテキストストリーミング(たとえば LLM の出力を stdout に流す)なら、こうです。
const response = await fetch('https://example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
process.stdout.write(decoder.decode(value, { stream: true }));
}
ストリーミングは、ネイティブ fetch と Got の両方が得意な領域です。Axios のストリーミング対応はやや限定的です。
fetch()だけでは構造化Webスクレイピングが難しい場面
HTTPでHTMLを取得できても、必要な項目を構造化データへ変換する処理は別に設計する必要があります。

fetchはHTTPクライアントであり、バイト列、テキスト、JSON、HTMLを取得します。一方、商品カード、価格、評価、連絡先テーブルといったページ上の意味を判定して抽出する機能は持っていません。fetchを中心に構造化Webスクレイピングを実装する場合、一般に次の処理が必要です。
- HTML をダウンロードするために
fetch()を使う - Cheerio(または同等ツール)で CSS セレクターを使って要素を選ぶ
- 独自のページネーション処理を書く
- ページがクライアント側レンダリングなら JavaScript を実行する
- プロキシ / bot 対策 / CAPTCHA を処理する
- サイトのレイアウトが変わるたびにセレクターを保守する
次は、fetchとCheerioで商品タイトルなどを抽出する約15行の例です。
import * as cheerio from 'cheerio';
const response = await fetch('https://example-store.com/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const $ = cheerio.load(html);
const products = $('.product-card')
.map((_, el) => ({
name: $(el).find('.product-title').text().trim(),
price: $(el).find('.price').text().trim(),
url: new URL($(el).find('a').attr('href'), response.url).href,
}))
.get();
console.log(products);
HTML構造が安定しているページでは、この方法で実装できます。一方、JavaScriptで描画されるコンテンツ、変化するクラス名、bot対策、ページネーションが加わる場合は、追加実装と継続的な保守が必要になります。
Thunderbit の Open API:生 HTML から構造化データへ、一回の呼び出しで
CSSセレクターの作成やページごとの解析処理が保守負担になる場合は、構造化抽出APIを比較できます。Thunderbitは、JavaScriptレンダリング、bot対策、レイアウト変更への対応を支援し、指定した項目を構造化データとして取得するためのAPIレイヤーを提供しています。
Distill API(POST /distill):指定したURLの内容をクリーンなMarkdownへ変換します。LLMへの入力、ナレッジベース作成、コンテンツ分析など、HTMLを直接解析したくない用途で利用できます。
Extract API(POST /extract):商品名、価格、評価など、取得したいデータをJSON Schemaで定義し、AIで抽出します。CSSセレクターの記述を減らし、レイアウト変更時の保守を支援します。
同じ商品スクレイピングをThunderbitのExtract APIから実行する例です。呼び出しにはネイティブfetchを使用できます。
const response = await fetch('https://openapi.thunderbit.com/openapi/v1/extract', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.THUNDERBIT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example-store.com/products',
renderMode: 'basic',
schema: {
type: 'object',
properties: {
products: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', description: '商品名' },
price: { type: 'string', description: '表示されている商品価格' },
rating: { type: 'number', description: '平均顧客評価' },
},
required: ['name', 'price'],
},
},
},
required: ['products'],
},
}),
});
if (!response.ok) throw new Error(`Thunderbit API: ${response.status}`);
const result = await response.json();
console.log(result.data);
この例では、fetch + Cheerioで約15行の抽出処理とセレクターを管理する代わりに、単一のAPI呼び出しでJSONを受け取ります。バッチ処理では、Thunderbit は一度の batch extract 呼び出しで最大 50 URL、batch distill 呼び出しで最大 100 URL に対応しています。
Thunderbitはfetchの置き換えではありません。fetchがHTTP通信を担当し、ThunderbitのAPIは取得対象を構造化するレイヤーとして使います。HTML構造が安定し、独自ロジックを管理できる場合はfetchとパーサーでも実装できます。JavaScriptレンダリングや複数ページへの対応、セレクター保守が負担になる場合は、抽出APIを比較する価値があります。
対象サイトの利用規約、著作権、個人情報、アクセス頻度を踏まえ、まず代表的なページでスキーマと抽出結果を検証してから処理範囲を広げます。料金が気になるなら、無料プラン で 600 API ユニットを試せます。月額プランは $6 からです。ブラウザ上でノーコード抽出ができる Thunderbit Chrome Extension もチェックできます。
構造化スクレイピングのアプローチをさらに知りたい方は、おすすめのデータ抽出ツール、ウェブスクレイパーの作り方、ウェブサイトのデータをExcelに取り込む最も簡単な方法 の各ガイドで、具体的なワークフローを詳しく解説しています。
Node.js Fetch API チートシート
ここまでの実装パターンを、用途別に参照できる形でまとめます。
| パターン | スニペット |
|---|---|
| 基本 GET | const res = await fetch(url); const data = await res.json(); |
| 基本 POST | await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); |
| HTTP エラーチェック | if (!res.ok) throw new Error(\\HTTP ${res.status}\); |
| タイムアウト(簡易) | await fetch(url, { signal: AbortSignal.timeout(5000) }); |
| 手動中断 | const c = new AbortController(); setTimeout(() => c.abort(), 5000); await fetch(url, { signal: c.signal }); |
| リトライ対象ステータス | 408、429、500、502、503、504 をリトライ。POST は安易にリトライしない。 |
| JSON ラッパー | fetchJSON() を使って ok を確認し、content type を解析し、HTTPError を投げる。 |
| 接続プール | import { Pool } from 'undici'; const pool = new Pool(origin, { connections: 10 }); fetch(url, { dispatcher: pool }); |
| チャンクのストリーム処理 | const reader = res.body.getReader(); await reader.read() をループする |
| 構造化抽出 | 生 HTML ではなく Web ページのフィールドが目的なら、Thunderbit Extract API を使う。 |
まとめと要点
2026年のNode.jsでは、ネイティブfetchを本番環境のHTTPクライアントとして利用できます。ただし、素のfetch()呼び出しだけで本番運用に必要なエラー処理や復旧条件がすべて揃うわけではありません。
本ガイドで扱った5つの実装ポイントは次のとおりです。
- エラーの落とし穴:
fetch()は 4xx/5xx では throw しません。必ずresponse.okを確認するか、fetchJSON()のようなラッパーを使いましょう。 - タイムアウト: 単純なケースなら
AbortSignal.timeout()を使います。AbortSignal.timeout()はTimeoutErrorを投げ、手動のcontroller.abort()はAbortErrorを投げます。 - リトライロジック: 組み込みではありません。冪等なリクエストと一時的な失敗には指数バックオフを追加しましょう。あるいは、fetch 風のリトライが最初からある Ky を使うのも手です。
- 接続の再利用: 高スループットのループでは、
dispatcherオプション経由で Undici のAgentかPoolを使いましょう。 - 構造化抽出: Web ページからデータが欲しいだけなら(生 HTML ではなく)、壊れやすい CSS セレクターを保守するより、Thunderbit のような抽出 API を検討しましょう。
選定時は、基本的なHTTP呼び出しならネイティブfetch、インターセプターが必要ならAxios、組み込みリトライやHTTP/2が必要ならGot、fetchに近いAPIで追加機能が必要ならKyを比較します。Webスクレイピングでセレクターやレンダリング処理の保守が大きくなった場合は、Thunderbitのような抽出APIも候補になります。
まずは現在のHTTP呼び出しを一つ選び、HTTPエラー、タイムアウト、再試行条件が明示されているかを見直してみてください。構造化抽出を試す場合は、代表的なページ一つで必要なフィールド、抽出結果、出力形式を検証してから対象を広げると判断しやすくなります。無料プランから試すことも、Thunderbit YouTube チャンネルのウォークスルーで操作を把握することもできます。
AI ウェブスクレイピングで Thunderbit を試す Get Started Free
FAQ
1. fetch は Node.js に組み込みですか?それともインストールが必要ですか?
fetch は Node.js 18 以降に組み込まれているので、インストールは不要です。Node 21 で安定版になり、Node 22 LTS と Node 24 LTS で正式にサポートされています。古い Node バージョンでは node-fetch の npm パッケージを使えますが、新しいプロジェクトはメンテナンスされている LTS を対象にするべきです。
2. fetch は 404 や 500 のレスポンスでエラーを投げますか?
いいえ。fetch が promise を reject するのは、ネットワークレベルの失敗(DNS エラー、接続なし、リクエスト中断)だけです。404、403、500 のような HTTP レスポンスは通常どおり解決され、そのとき response.ok === false になります。response.ok か response.status を明示的に確認する必要があります。あるいは、このガイドで示した fetchJSON() 関数のようなラッパーを使ってください。
3. Node.js の fetch にタイムアウトを追加するには?
最も簡単なのは、Node 18.11+ で使える AbortSignal.timeout(ms) です。await fetch(url, { signal: AbortSignal.timeout(5000) }) のように書きます。リクエストが 5 秒を超えると TimeoutError が投げられます。より細かく制御したいなら、AbortController を手動で作成し、setTimeout から controller.abort() を呼びます。手動パターンでは AbortError、AbortSignal.timeout() では TimeoutError を捕捉してください。
4. Node.js で fetch を Web スクレイピングに使えますか?
はい。fetchで生HTMLを取得し、Cheerioなどのパーサーで必要な要素を抽出できます。ページネーション、JavaScriptレンダリング、bot対策が必要なサイトでは、追加の実装と保守も必要です。商品名、価格、連絡先情報などをJSONで取得したい場合は、CSSセレクターの管理を減らす手段としてThunderbit の Extract APIも比較できます。いずれの方法でも、対象サイトの利用規約、取得データ、アクセス頻度を踏まえて運用します。
5. 2026 年に Axios からネイティブ fetch に切り替えるべきですか?
Node 22+の新しいプロジェクトで基本的なHTTP呼び出しが中心なら、ネイティブfetchは移行候補になります。一方、リクエスト/レスポンスのインターセプター、HTTPエラー時の自動reject、古いNodeとの互換性を利用している場合は、Axiosを維持する判断もあります。移行前に、現在使用している機能、エラー処理、タイムアウト、テスト範囲を洗い出し、置き換え後の挙動を比較します。
さらに詳しく


