概要
Node.js 18以降のネイティブfetch、またはnode-fetchライブラリを使用した際に発生するFailed to fetchエラーについて、Node.js環境固有の原因と解決策を解説します。
ブラウザ環境との違いや、サーバーサイド特有の問題に焦点を当てています。
汎用的な原因と解決策はFailed to fetchを参照してください。
エラーメッセージ
TypeError: Failed to fetch
TypeError: fetch failed
cause: Error: connect ECONNREFUSED 127.0.0.1:3000
TypeError: fetch failed
cause: Error: getaddrinfo ENOTFOUND api.example.com
Node.js固有の原因
原因1: DNS解決の問題(IPv4/IPv6)
Node.jsはデフォルトでIPv6を優先することがあり、IPv4のみ対応しているサーバーへの接続が失敗します。
確認方法:
1
2
| # DNSの解決結果を確認
node -e "require('dns').lookup('localhost', { all: true }, (err, addresses) => console.log(addresses))"
|
解決策:
1
2
3
4
5
6
7
| import dns from 'dns';
// IPv4を優先する設定
dns.setDefaultResultOrder('ipv4first');
// その後fetchを実行
const response = await fetch('http://localhost:3000/api/data');
|
または、明示的にIPアドレスを指定:
1
2
| // localhostの代わりに127.0.0.1を使用
const response = await fetch('http://127.0.0.1:3000/api/data');
|
原因2: 自己署名証明書・プライベートCA
開発環境やイントラネットで自己署名証明書を使用している場合、SSL検証に失敗します。
確認方法:
1
2
| # 証明書の検証
openssl s_client -connect internal-api.example.com:443
|
解決策(node-fetch使用時):
1
2
3
4
5
6
7
8
9
10
11
| import https from 'https';
import fs from 'fs';
// カスタムCAを指定
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/custom-ca.pem')
});
const response = await fetch('https://internal-api.example.com', {
agent
});
|
解決策(Node.js 18+ ネイティブfetch):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import https from 'https';
import fs from 'fs';
// undiciのdispatcherを使用
import { Agent } from 'undici';
const dispatcher = new Agent({
connect: {
ca: fs.readFileSync('/path/to/custom-ca.pem')
}
});
const response = await fetch('https://internal-api.example.com', {
dispatcher
});
|
開発環境のみ(非推奨):
1
2
| // 本番環境では絶対に使わないこと
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
原因3: プロキシ設定
企業ネットワークなどでプロキシが必要な環境で、プロキシ設定が行われていない場合。
確認方法:
1
2
3
| # 環境変数を確認
echo $HTTP_PROXY
echo $HTTPS_PROXY
|
解決策:
1
2
3
4
5
6
7
| import { ProxyAgent } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
const response = await fetch('https://api.example.com', {
dispatcher: proxyAgent
});
|
または環境変数を設定:
1
2
| export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
|
原因4: タイムアウト設定
デフォルトのタイムアウトが短い、または設定されていない場合。
解決策:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // AbortControllerでタイムアウトを設定
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30秒
try {
const response = await fetch('https://api.example.com/slow-endpoint', {
signal: controller.signal
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out');
}
throw error;
}
|
原因5: Keep-Alive接続の問題
サーバー側でKeep-Alive接続が切断された後に再利用しようとした場合。
解決策:
1
2
3
4
5
6
7
8
9
10
| import { Agent } from 'undici';
const agent = new Agent({
keepAliveTimeout: 10000, // 10秒
keepAliveMaxTimeout: 30000 // 最大30秒
});
const response = await fetch('https://api.example.com', {
dispatcher: agent
});
|
デバッグ方法
詳細なエラー情報を取得
1
2
3
4
5
6
7
| try {
const response = await fetch('https://api.example.com');
} catch (error) {
console.error('Error:', error.message);
console.error('Cause:', error.cause); // Node.js固有の詳細情報
console.error('Code:', error.cause?.code); // ECONNREFUSED, ENOTFOUND等
}
|
NODE_DEBUGで詳細ログ
1
| NODE_DEBUG=fetch,undici node your-script.js
|
よくある間違い
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'を本番環境で使用する- IPv6環境を考慮せずにlocalhostを使用する
- プロキシ環境でプロキシ設定を忘れる
- node-fetchとネイティブfetchのAPI差異を見落とす
まだ解決しない場合
→ 診断ハブに戻る
関連エラー