Node.jsでサーバーサイドアプリケーションを開発する際、外部APIとの通信は避けて通れません。REST APIからのデータ取得、Webhookの送信、マイクロサービス間の連携など、HTTPクライアント機能はあらゆる場面で必要になります。

この記事では、Node.jsでHTTPリクエストを送信する2つの主要な方法、グローバルfetch() APIと組み込みのhttpsモジュールについて解説します。それぞれの特徴を理解し、ユースケースに応じた使い分けができるようになることを目指します。

実行環境と前提条件

項目 バージョン
Node.js 20.x LTS以上
npm 10.x以上
OS Windows/macOS/Linux

前提条件として、JavaScriptの基礎知識とNode.jsの基本的なAPI理解があることを想定しています。

fetch APIとhttpsモジュールの比較

Node.jsでHTTPリクエストを送信する方法は主に2つあります。

項目 fetch API https/httpモジュール
導入バージョン Node.js 18以降(v21で安定版) 初期から組み込み
API設計 Promiseベース、ブラウザ互換 コールバック/ストリームベース
学習コスト 低い(ブラウザと同じ) 中程度
柔軟性 標準的なユースケース向け 細かい制御が可能
ストリーミング Response.bodyでサポート ネイティブサポート
タイムアウト設定 AbortSignal.timeout()を使用 socket.setTimeout()で設定

一般的なAPIリクエストにはfetch()が推奨されますが、低レベルな制御やレガシー環境との互換性が必要な場合はhttpsモジュールを使用します。

グローバルfetch APIでHTTPリクエストを送信する

fetch APIの概要

Node.js 18からグローバルなfetch()関数が利用可能になりました。Node.js 21以降では安定版(Stable)として正式にサポートされています。ブラウザのFetch APIとほぼ同じインターフェースを持つため、フロントエンドとバックエンドで共通のコードを書けるメリットがあります。

1
2
3
4
// グローバルfetchはimport不要で使用可能
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

GETリクエストを送信する

最もシンプルなGETリクエストの例を見てみましょう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
async function fetchData() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    console.log('取得したデータ:', data);
    return data;
  } catch (error) {
    console.error('リクエストに失敗しました:', error.message);
    throw error;
  }
}

// 実行
fetchData();

fetch()はネットワークエラーの場合のみ例外をスローし、HTTPエラーステータス(4xx、5xx)では例外をスローしない点に注意が必要です。response.okプロパティを確認して、ステータスコードが200-299の範囲内かどうかを判定します。

POSTリクエストを送信する

JSONデータをPOSTリクエストで送信する例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
async function createPost(postData) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(postData)
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const result = await response.json();
    console.log('作成されたデータ:', result);
    return result;
  } catch (error) {
    console.error('リクエストに失敗しました:', error.message);
    throw error;
  }
}

// 実行
createPost({
  title: 'Node.jsでHTTPリクエスト',
  body: 'fetch APIを使った実装例です',
  userId: 1
});

リクエストヘッダーの設定

カスタムヘッダーを設定する方法を紹介します。認証トークンやAPIキーの送信によく使われます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
async function fetchWithAuth(url, token) {
  const response = await fetch(url, {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
      'X-Custom-Header': 'custom-value'
    }
  });
  
  return response.json();
}

Headersオブジェクトを使用することもできます。

1
2
3
4
5
6
7
8
const headers = new Headers();
headers.append('Authorization', 'Bearer your-token');
headers.append('Content-Type', 'application/json');

const response = await fetch('https://api.example.com/data', {
  method: 'GET',
  headers: headers
});

PUT/PATCH/DELETEリクエスト

REST APIでよく使用するその他のHTTPメソッドの例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// PUTリクエスト(リソースの完全な更新)
async function updatePost(id, postData) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(postData)
  });
  
  return response.json();
}

// PATCHリクエスト(リソースの部分的な更新)
async function patchPost(id, partialData) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(partialData)
  });
  
  return response.json();
}

// DELETEリクエスト
async function deletePost(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
    method: 'DELETE'
  });
  
  return response.ok;
}

レスポンスの処理

fetch()のレスポンスはさまざまな形式で取得できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
async function handleResponse(url) {
  const response = await fetch(url);
  
  // JSONとして取得
  const jsonData = await response.json();
  
  // テキストとして取得
  const textData = await response.text();
  
  // ArrayBufferとして取得(バイナリデータ)
  const arrayBuffer = await response.arrayBuffer();
  
  // Blobとして取得
  const blob = await response.blob();
  
  return jsonData;
}

レスポンスのメタ情報も取得できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
async function inspectResponse(url) {
  const response = await fetch(url);
  
  console.log('ステータスコード:', response.status);
  console.log('ステータステキスト:', response.statusText);
  console.log('成功フラグ:', response.ok);
  console.log('リダイレクト:', response.redirected);
  console.log('レスポンスURL:', response.url);
  
  // ヘッダーの取得
  console.log('Content-Type:', response.headers.get('content-type'));
  console.log('Content-Length:', response.headers.get('content-length'));
  
  // すべてのヘッダーを列挙
  for (const [key, value] of response.headers) {
    console.log(`${key}: ${value}`);
  }
}

タイムアウトの設定

AbortControllerAbortSignal.timeout()を使用してタイムアウトを設定します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// AbortSignal.timeout()を使用(Node.js 18.3以降)
async function fetchWithTimeout(url, timeoutMs = 5000) {
  try {
    const response = await fetch(url, {
      signal: AbortSignal.timeout(timeoutMs)
    });
    
    return response.json();
  } catch (error) {
    if (error.name === 'TimeoutError') {
      console.error(`リクエストがタイムアウトしました(${timeoutMs}ms)`);
    } else if (error.name === 'AbortError') {
      console.error('リクエストが中断されました');
    }
    throw error;
  }
}

手動でAbortControllerを使用する方法もあります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
async function fetchWithAbortController(url, timeoutMs = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
  
  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    
    return response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      throw new Error(`リクエストがタイムアウトしました(${timeoutMs}ms)`);
    }
    throw error;
  } finally {
    clearTimeout(timeoutId);
  }
}

エラーハンドリングのベストプラクティス

堅牢なエラーハンドリングを実装した例です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class HttpError extends Error {
  constructor(response) {
    super(`HTTP Error: ${response.status} ${response.statusText}`);
    this.name = 'HttpError';
    this.status = response.status;
    this.statusText = response.statusText;
    this.response = response;
  }
}

async function robustFetch(url, options = {}) {
  const defaultOptions = {
    signal: AbortSignal.timeout(10000), // デフォルト10秒タイムアウト
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    }
  };
  
  const mergedOptions = {
    ...defaultOptions,
    ...options,
    headers: {
      ...defaultOptions.headers,
      ...options.headers
    }
  };
  
  try {
    const response = await fetch(url, mergedOptions);
    
    if (!response.ok) {
      throw new HttpError(response);
    }
    
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return await response.json();
    }
    
    return await response.text();
  } catch (error) {
    if (error.name === 'TimeoutError') {
      throw new Error('リクエストがタイムアウトしました');
    }
    if (error.name === 'TypeError') {
      throw new Error('ネットワークエラーが発生しました');
    }
    throw error;
  }
}

httpsモジュールでHTTPリクエストを送信する

httpsモジュールの概要

httpsモジュール(およびhttpモジュール)は、Node.jsに最初から組み込まれている低レベルなHTTPクライアント機能を提供します。fetch()と比較すると冗長ですが、細かい制御が可能です。

1
2
3
const https = require('node:https');
// または
import https from 'node:https';

https.get()でGETリクエストを送信する

シンプルなGETリクエストにはhttps.get()メソッドを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const https = require('node:https');

function fetchData(url) {
  return new Promise((resolve, reject) => {
    https.get(url, (res) => {
      let data = '';
      
      // データを受信するたびに呼ばれる
      res.on('data', (chunk) => {
        data += chunk;
      });
      
      // レスポンス完了時に呼ばれる
      res.on('end', () => {
        try {
          const jsonData = JSON.parse(data);
          resolve(jsonData);
        } catch (error) {
          reject(new Error('JSONのパースに失敗しました'));
        }
      });
    }).on('error', (error) => {
      reject(error);
    });
  });
}

// 実行
fetchData('https://jsonplaceholder.typicode.com/posts/1')
  .then(data => console.log('取得したデータ:', data))
  .catch(error => console.error('エラー:', error.message));

https.request()でPOSTリクエストを送信する

より柔軟なリクエストにはhttps.request()メソッドを使用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const https = require('node:https');

function postData(url, postData) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(postData);
    
    const urlObj = new URL(url);
    const options = {
      hostname: urlObj.hostname,
      port: urlObj.port || 443,
      path: urlObj.pathname,
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(data)
      }
    };
    
    const req = https.request(options, (res) => {
      let responseData = '';
      
      res.on('data', (chunk) => {
        responseData += chunk;
      });
      
      res.on('end', () => {
        try {
          const jsonData = JSON.parse(responseData);
          resolve({
            statusCode: res.statusCode,
            headers: res.headers,
            data: jsonData
          });
        } catch (error) {
          reject(new Error('JSONのパースに失敗しました'));
        }
      });
    });
    
    req.on('error', (error) => {
      reject(error);
    });
    
    // リクエストボディを送信
    req.write(data);
    req.end();
  });
}

// 実行
postData('https://jsonplaceholder.typicode.com/posts', {
  title: 'Node.js httpsモジュール',
  body: 'https.request()を使った実装例です',
  userId: 1
})
  .then(result => console.log('作成されたデータ:', result))
  .catch(error => console.error('エラー:', error.message));

リクエストオプションの詳細設定

https.request()では、さまざまなオプションを設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const https = require('node:https');

const options = {
  hostname: 'api.example.com',
  port: 443,
  path: '/api/v1/users',
  method: 'GET',
  headers: {
    'Authorization': 'Bearer your-token',
    'Accept': 'application/json',
    'User-Agent': 'Node.js HTTPS Client'
  },
  // タイムアウト設定(ミリ秒)
  timeout: 5000,
  // TLS/SSL設定
  rejectUnauthorized: true, // 証明書検証を行う(本番環境では必須)
  // キープアライブ設定
  agent: new https.Agent({
    keepAlive: true,
    maxSockets: 10
  })
};

const req = https.request(options, (res) => {
  console.log('ステータスコード:', res.statusCode);
  console.log('ヘッダー:', res.headers);
  
  res.on('data', (chunk) => {
    console.log('受信データ:', chunk.toString());
  });
});

req.on('timeout', () => {
  console.error('リクエストがタイムアウトしました');
  req.destroy();
});

req.on('error', (error) => {
  console.error('エラー:', error.message);
});

req.end();

タイムアウトの設定

httpsモジュールでは、複数の方法でタイムアウトを設定できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const https = require('node:https');

function fetchWithTimeout(url, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    
    const options = {
      hostname: urlObj.hostname,
      port: urlObj.port || 443,
      path: urlObj.pathname + urlObj.search,
      method: 'GET',
      timeout: timeoutMs
    };
    
    const req = https.request(options, (res) => {
      let data = '';
      
      res.on('data', (chunk) => {
        data += chunk;
      });
      
      res.on('end', () => {
        resolve({
          statusCode: res.statusCode,
          data: JSON.parse(data)
        });
      });
    });
    
    // タイムアウトイベント
    req.on('timeout', () => {
      req.destroy();
      reject(new Error(`リクエストがタイムアウトしました(${timeoutMs}ms)`));
    });
    
    req.on('error', (error) => {
      reject(error);
    });
    
    req.end();
  });
}

ソケットレベルでのタイムアウト設定も可能です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const https = require('node:https');

function fetchWithSocketTimeout(url, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const urlObj = new URL(url);
    
    const req = https.request({
      hostname: urlObj.hostname,
      path: urlObj.pathname,
      method: 'GET'
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    });
    
    req.on('socket', (socket) => {
      socket.setTimeout(timeoutMs);
      socket.on('timeout', () => {
        req.destroy();
        reject(new Error('ソケットタイムアウト'));
      });
    });
    
    req.on('error', reject);
    req.end();
  });
}

HTTPリクエストのストリーミング

大きなファイルをダウンロードする際は、ストリームを活用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const https = require('node:https');
const fs = require('node:fs');
const path = require('node:path');

function downloadFile(url, destPath) {
  return new Promise((resolve, reject) => {
    const file = fs.createWriteStream(destPath);
    
    https.get(url, (res) => {
      if (res.statusCode !== 200) {
        reject(new Error(`ダウンロード失敗: ステータスコード ${res.statusCode}`));
        return;
      }
      
      const totalSize = parseInt(res.headers['content-length'], 10);
      let downloadedSize = 0;
      
      res.on('data', (chunk) => {
        downloadedSize += chunk.length;
        const progress = ((downloadedSize / totalSize) * 100).toFixed(2);
        process.stdout.write(`\rダウンロード進捗: ${progress}%`);
      });
      
      res.pipe(file);
      
      file.on('finish', () => {
        file.close();
        console.log('\nダウンロード完了');
        resolve(destPath);
      });
    }).on('error', (error) => {
      fs.unlink(destPath, () => {}); // 部分的にダウンロードしたファイルを削除
      reject(error);
    });
  });
}

// 実行例
downloadFile(
  'https://example.com/large-file.zip',
  path.join(__dirname, 'downloaded-file.zip')
);

実践的なHTTPクライアントの実装

再利用可能なHTTPクライアントクラス

実際のプロジェクトで使える汎用的なHTTPクライアントクラスを実装します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class HttpClient {
  constructor(baseUrl, defaultHeaders = {}) {
    this.baseUrl = baseUrl;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      ...defaultHeaders
    };
    this.defaultTimeout = 10000;
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const config = {
      ...options,
      headers: {
        ...this.defaultHeaders,
        ...options.headers
      },
      signal: options.signal || AbortSignal.timeout(this.defaultTimeout)
    };
    
    try {
      const response = await fetch(url, config);
      
      if (!response.ok) {
        const errorBody = await response.text();
        throw new Error(`HTTP ${response.status}: ${errorBody}`);
      }
      
      const contentType = response.headers.get('content-type');
      if (contentType?.includes('application/json')) {
        return await response.json();
      }
      return await response.text();
    } catch (error) {
      if (error.name === 'TimeoutError') {
        throw new Error(`リクエストタイムアウト: ${url}`);
      }
      throw error;
    }
  }
  
  async get(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'GET' });
  }
  
  async post(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  async put(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  async patch(endpoint, data, options = {}) {
    return this.request(endpoint, {
      ...options,
      method: 'PATCH',
      body: JSON.stringify(data)
    });
  }
  
  async delete(endpoint, options = {}) {
    return this.request(endpoint, { ...options, method: 'DELETE' });
  }
  
  setAuthToken(token) {
    this.defaultHeaders['Authorization'] = `Bearer ${token}`;
  }
}

// 使用例
const api = new HttpClient('https://jsonplaceholder.typicode.com');

// GETリクエスト
const posts = await api.get('/posts');

// POSTリクエスト
const newPost = await api.post('/posts', {
  title: '新しい投稿',
  body: '本文',
  userId: 1
});

// 認証トークンを設定
api.setAuthToken('your-jwt-token');

リトライ機能の実装

ネットワークエラー時に自動リトライする機能を追加します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
async function fetchWithRetry(url, options = {}, maxRetries = 3, delay = 1000) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, {
        ...options,
        signal: AbortSignal.timeout(options.timeout || 10000)
      });
      
      if (!response.ok && response.status >= 500) {
        throw new Error(`サーバーエラー: ${response.status}`);
      }
      
      return response;
    } catch (error) {
      lastError = error;
      console.warn(`リクエスト失敗(${attempt}/${maxRetries}回目): ${error.message}`);
      
      if (attempt < maxRetries) {
        // 指数バックオフ
        const waitTime = delay * Math.pow(2, attempt - 1);
        console.log(`${waitTime}ms後にリトライします...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
      }
    }
  }
  
  throw new Error(`${maxRetries}回のリトライ後も失敗: ${lastError.message}`);
}

// 使用例
async function reliableFetch() {
  try {
    const response = await fetchWithRetry('https://api.example.com/data', {
      method: 'GET',
      headers: { 'Accept': 'application/json' }
    }, 3, 1000);
    
    return await response.json();
  } catch (error) {
    console.error('最終的にリクエストが失敗しました:', error.message);
    throw error;
  }
}

並列リクエストと直列リクエスト

複数のAPIを効率的に呼び出すパターンを紹介します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 並列リクエスト(すべてのリクエストを同時に送信)
async function fetchAllInParallel(urls) {
  const promises = urls.map(url => fetch(url).then(res => res.json()));
  const results = await Promise.all(promises);
  return results;
}

// 一部が失敗しても継続する並列リクエスト
async function fetchAllSettled(urls) {
  const promises = urls.map(url => fetch(url).then(res => res.json()));
  const results = await Promise.allSettled(promises);
  
  return results.map((result, index) => ({
    url: urls[index],
    status: result.status,
    data: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason.message : null
  }));
}

// 直列リクエスト(順番に実行)
async function fetchSequentially(urls) {
  const results = [];
  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.json();
    results.push(data);
  }
  return results;
}

// 使用例
const urls = [
  'https://jsonplaceholder.typicode.com/posts/1',
  'https://jsonplaceholder.typicode.com/posts/2',
  'https://jsonplaceholder.typicode.com/posts/3'
];

const parallelResults = await fetchAllInParallel(urls);
console.log('並列リクエスト結果:', parallelResults);

fetch APIとhttpsモジュールの使い分け

fetch APIを選ぶべきケース

以下のような場合はfetch()を使用することを推奨します。

  • 一般的なREST API通信
  • ブラウザとNode.jsで共通のコードを使いたい場合
  • 新規プロジェクトでNode.js 18以降を使用する場合
  • シンプルで読みやすいコードを優先する場合
1
2
3
4
5
6
// シンプルで読みやすいfetchの例
async function getUser(userId) {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  if (!response.ok) throw new Error('User not found');
  return response.json();
}

httpsモジュールを選ぶべきケース

以下のような場合はhttpsモジュールを使用することを検討します。

  • Node.js 18未満の環境をサポートする必要がある場合
  • 細かいソケット制御が必要な場合
  • カスタムエージェント(接続プール)を管理したい場合
  • TLS/SSL設定を細かく制御したい場合
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// カスタムエージェントを使用した例
const https = require('node:https');

const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 25,
  maxFreeSockets: 10,
  timeout: 60000
});

// 高頻度なAPI呼び出しで接続を再利用
function makeRequest(path) {
  return new Promise((resolve, reject) => {
    const req = https.request({
      hostname: 'api.example.com',
      path: path,
      agent: agent
    }, (res) => {
      // レスポンス処理
    });
    req.end();
  });
}

HTTPリクエストのフロー

以下のフローチャートは、HTTPリクエストの処理フローを示しています。

flowchart TD
    A[リクエスト開始] --> B{Node.js バージョン}
    B -->|18以上| C[fetch API使用]
    B -->|18未満| D[https/httpモジュール使用]
    
    C --> E[リクエスト送信]
    D --> E
    
    E --> F{レスポンス受信}
    F -->|成功| G[ステータスコード確認]
    F -->|タイムアウト| H[タイムアウトエラー]
    F -->|ネットワークエラー| I[接続エラー]
    
    G -->|2xx| J[レスポンス処理]
    G -->|4xx| K[クライアントエラー処理]
    G -->|5xx| L[サーバーエラー処理]
    
    J --> M[JSONパース]
    M --> N[データ返却]
    
    H --> O[リトライ判定]
    I --> O
    L --> O
    
    O -->|リトライ| E
    O -->|リトライ上限| P[エラー返却]
    K --> P

まとめ

この記事では、Node.jsでHTTPリクエストを送信する2つの主要な方法について解説しました。

  • fetch API: Node.js 18以降で利用可能なモダンなHTTPクライアント。Promiseベースでブラウザと互換性があり、一般的なAPIリクエストに最適
  • httpsモジュール: Node.jsに組み込まれた低レベルなHTTPクライアント。細かい制御が必要な場合やレガシー環境で使用

新規プロジェクトではfetch APIの使用を推奨しますが、要件に応じてhttpsモジュールも選択肢として持っておくことが重要です。どちらを使用する場合も、適切なエラーハンドリング、タイムアウト設定、リトライ処理を実装することで、信頼性の高いHTTPクライアントを構築できます。

参考リンク