JavaScriptでの外部API連携

2025-07-28

はじめに

現代のWeb開発において、外部APIとの連携はほぼ必須のスキルです。この章では、JavaScriptを使って外部APIと通信する方法を、初心者の方にもわかりやすく詳細に解説します。API連携の基本概念から、実際のコード例、エラー処理、ベストプラクティスまで、実践的な知識を身につけましょう。

APIとは何か?

基本的な定義

API(Application Programming Interface)は、ソフトウェア同士が通信するためのインターフェースです。Web APIはHTTPプロトコルを使用してデータをやり取りします。

APIの種類

  1. RESTful API: HTTPメソッド(GET, POSTなど)を使用
  2. GraphQL API: クエリ言語を使用して必要なデータのみ取得
  3. SOAP API: XMLベースのプロトコル(現在はあまり使われない)
  4. WebSocket: 双方向リアルタイム通信

API連携の基本要素

1. エンドポイント(URL)

APIが提供する特定の機能へのアクセスポイントです。
例: https://api.example.com/users

2. HTTPメソッド

  • GET: データの取得
  • POST: データの作成
  • PUT/PATCH: データの更新
  • DELETE: データの削除

3. リクエストヘッダー

追加情報を提供します。よく使われるヘッダー:

  • Content-Type: データ形式(application/jsonなど)
  • Authorization: 認証トークン
  • Accept: 受け入れ可能なレスポンス形式

4. リクエストボディ

POSTやPUTリクエストでサーバーに送信するデータ

5. クエリパラメータ

URLの末尾に追加するパラメータ
例: ?page=1&limit=10

6. ステータスコード

レスポンスの状態を示す3桁の数字:

  • 200番台: 成功
  • 400番台: クライアントエラー
  • 500番台: サーバーエラー

fetch APIを使った基本的なAPI連携

現代のJavaScriptでは、fetch APIが標準的なHTTPリクエストの方法です。

GETリクエストの例

async function fetchUsers() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const users = await response.json();
    console.log('取得したユーザー:', users);
    return users;
  } catch (error) {
    console.error('ユーザー取得に失敗:', error);
    throw error;
  }
}

fetchUsers().then(users => console.log('処理完了'));

POSTリクエストの例

async function createUser(userData) {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const newUser = await response.json();
    console.log('作成されたユーザー:', newUser);
    return newUser;
  } catch (error) {
    console.error('ユーザー作成に失敗:', error);
    throw error;
  }
}

const newUser = {
  name: 'John Doe',
  email: 'john@example.com'
};

createUser(newUser);

認証が必要なAPIの扱い方

多くのAPIは認証が必要です。主な認証方法:

1. APIキー

const apiKey = 'your_api_key_here';

async function fetchWeather(city) {
  const response = await fetch(
    `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`
  );
  // ...
}

2. Bearerトークン(JWTなど)

const token = 'your_token_here';

async function fetchProtectedData() {
  const response = await fetch('https://api.example.com/protected', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  // ...
}

3. OAuth

より複雑ですが、多くの公開APIで使用されています。

実際のAPI連携のステップバイステップ

1. APIドキュメントを読む

  • 利用可能なエンドポイント
  • 必要なパラメータ
  • 認証方法
  • レートリミット
  • レスポンスの形式

2. ベースURLを設定

const API_BASE_URL = 'https://api.example.com/v1';

3. リクエスト関数を作成

async function makeApiRequest(endpoint, method = 'GET', body = null) {
  const url = `${API_BASE_URL}${endpoint}`;
  const options = {
    method,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${localStorage.getItem('token')}`
    },
  };

  if (body) {
    options.body = JSON.stringify(body);
  }

  try {
    const response = await fetch(url, options);

    if (response.status === 401) {
      // 認証エラー処理
      handleUnauthorized();
      throw new Error('認証が必要です');
    }

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.message || 'リクエストに失敗しました');
    }

    return await response.json();
  } catch (error) {
    console.error(`APIリクエストエラー (${method} ${endpoint}):`, error);
    throw error;
  }
}

4. 具体的なAPI関数を実装

// ユーザー一覧取得
async function getUsers() {
  return makeApiRequest('/users');
}

// 特定ユーザー取得
async function getUser(id) {
  return makeApiRequest(`/users/${id}`);
}

// ユーザー作成
async function createUser(userData) {
  return makeApiRequest('/users', 'POST', userData);
}

よくある問題と解決策

1. CORSエラー

ブラウザのセキュリティ制約によるエラーです。

解決策:

  • サーバー側でCORS設定を修正(Access-Control-Allow-Originなど)
  • 開発時はプロキシサーバーを使用
  • 必要に応じてモードを設定(非推奨)
fetch(url, {
  mode: 'cors' // または 'no-cors'(制限あり)
});

2. レートリミット

APIの利用制限に達した場合のエラー。

解決策:

  • リクエスト間隔を空ける
  • エラー時のリトライ処理を実装
  • レスポンスヘッダーを確認(X-RateLimit-*など)
async function fetchWithRetry(url, retries = 3) {
  try {
    const response = await fetch(url);
    return response;
  } catch (error) {
    if (retries <= 0) throw error;
    await new Promise(resolve => setTimeout(resolve, 1000));
    return fetchWithRetry(url, retries - 1);
  }
}

3. ネットワークエラー

解決策:

  • タイムアウト設定
  • エラーハンドリングの強化
async function fetchWithTimeout(url, timeout = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
}

ベストプラクティス

  1. エラーハンドリングを徹底: ネットワークエラー、APIエラー、データパースエラーなど
  2. リクエストを抽象化: 共通関数を作成して重複を減らす
  3. セキュリティを考慮: APIキーをフロントエンドにハードコードしない
  4. パフォーマンス最適化: キャッシュ、並列リクエスト、不要なリクエストの削減
  5. テストを書く: API連携部分のテストを実装
  6. ログを残す: デバッグ用に適切なログを記録
  7. 型定義を使用: TypeScriptでAPIレスポンスの型を定義

実際のプロジェクトでのAPI連携例

天気情報アプリ

class WeatherApi {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.openweathermap.org/data/2.5';
  }

  async getCurrentWeather(city) {
    const response = await fetch(
      `${this.baseUrl}/weather?q=${city}&appid=${this.apiKey}&units=metric`
    );

    if (!response.ok) {
      throw new Error(await this.getErrorMessage(response));
    }

    return await response.json();
  }

  async getErrorMessage(response) {
    try {
      const errorData = await response.json();
      return errorData.message || '天気情報の取得に失敗しました';
    } catch {
      return `HTTPエラー: ${response.status}`;
    }
  }
}

// 使用例
const weatherApi = new WeatherApi('your_api_key');

async function displayWeather() {
  try {
    const weather = await weatherApi.getCurrentWeather('Tokyo');
    console.log(`東京の気温: ${weather.main.temp}°C`);
  } catch (error) {
    console.error('エラー:', error.message);
  }
}

displayWeather();

まとめ

外部API連携の重要なポイント:

  • 現代のWeb開発ではAPI連携が不可欠
  • fetch APIが標準的な方法
  • 適切なエラーハンドリングが重要
  • 認証が必要なAPIが多い
  • CORSなどのセキュリティ制約を理解
  • コードを整理し、再利用可能な関数を作成
  • パフォーマンスとセキュリティを考慮
  • APIドキュメントをよく読むことが成功の鍵

API連携をマスターすると、外部サービスと連携した豊かなアプリケーションを作成できるようになります。

練習問題

問題1

以下のfetchリクエストにはいくつかの問題があります。それを指摘し、修正したコードを書いてください。

async function getPosts() {
  const response = fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = response.json();
  return posts;
}

問題2

次のAPI連携に関する記述について、正しいものには○、間違っているものには×をつけてください。

  1. APIキーはフロントエンドコードに直接書いても問題ない ( )
  2. fetch APIはPromiseを返す ( )
  3. CORSエラーはサーバー側の設定で回避できる ( )
  4. POSTリクエストではbodyにJSON文字列を指定する必要がある ( )

問題3

以下の要件を満たすAPIクライアントクラスを作成してください。

  • ベースURLをコンストラクタで設定
  • GET, POST, PUT, DELETEメソッドを実装
  • すべてのリクエストに認証トークンを自動で付加
  • エラーハンドリングを統一
  • レスポンスのContent-Typeがapplication/jsonであることを確認

解答例

問題1の解答

問題点:

  1. awaitの欠如(fetchとjson()の両方)
  2. エラーハンドリングがない
  3. レスポンスのステータスチェックがない

修正コード:

async function getPosts() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/posts');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const posts = await response.json();
    return posts;
  } catch (error) {
    console.error('投稿の取得に失敗:', error);
    throw error;
  }
}

問題2の解答

  1. × (APIキーは環境変数などで管理すべき)
  2. ○ (JSONを送信する場合)

問題3の解答

class ApiClient {
  constructor(baseUrl, authToken) {
    this.baseUrl = baseUrl;
    this.authToken = authToken;
  }

  async request(endpoint, method = 'GET', body = null) {
    const url = `${this.baseUrl}${endpoint}`;
    const options = {
      method,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.authToken}`
      }
    };

    if (body) {
      options.body = JSON.stringify(body);
    }

    try {
      const response = await fetch(url, options);

      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw new Error(errorData.message || 'リクエストに失敗しました');
      }

      const contentType = response.headers.get('content-type');
      if (!contentType || !contentType.includes('application/json')) {
        throw new Error('無効なコンテンツタイプ');
      }

      return await response.json();
    } catch (error) {
      console.error(`APIリクエストエラー (${method} ${endpoint}):`, error);
      throw error;
    }
  }

  get(endpoint) {
    return this.request(endpoint);
  }

  post(endpoint, body) {
    return this.request(endpoint, 'POST', body);
  }

  put(endpoint, body) {
    return this.request(endpoint, 'PUT', body);
  }

  delete(endpoint) {
    return this.request(endpoint, 'DELETE');
  }
}

// 使用例
const api = new ApiClient('https://api.example.com/v1', 'your_token');

// GETリクエスト
api.get('/users').then(users => console.log(users));

// POSTリクエスト
api.post('/users', { name: 'John' }).then(user => console.log(user));