😈 API連携の悪夢:CORSクロスドメイン問題とRate Limit

前のチュートリアルでPostmanを使ってAPI接続に成功し、自分を天才だと思ったあなた。 さっそくfetchをウェブページに実装し、ブラウザで実行すると:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

画面は真っ白、Consoleには赤いエラーが大量に。おめでとうございます、あなたはフロントエンドエンジニアの「通過儀礼」である**CORS(クロスオリジンリソース共有)**に正式に遭遇しました。

この章では、CORSの正体と、もう一つの強敵**Rate Limit(リクエスト頻度制限)**との戦い方を徹底解説します。


1. CORSクロスオリジンリソース共有とは?

CORS (Cross-Origin Resource Sharing) はブラウザのセキュリティメカニズムです。

重要なのは「ブラウザ」という点。 Postmanはブラウザではないし、Node.jsで書いたバックエンドスクリプトもブラウザではありません。したがって、これらでAPIを呼び出してもCORSに阻まれることはありません。「ウェブフロントエンド(React、Vue、純粋なHTML内のJavaScriptなど)」でfetchaxiosを使って**「異なるドメイン」**のAPIを呼び出す時だけ、ブラウザはこの防護盾を発動します。

なぜこの防護盾が必要なのか?

銀行サイトにログインした後、誤って詐欺サイト(evil.com)を開いたと想像してください。 CORSがない場合、この詐欺サイトは背後でJavaScriptを使って銀行のAPIを呼び出せます:fetch('https://bank.com/api/transfer')、そしてあなたの口座からお金を引き出せてしまいます!

これを防ぐため、ブラウザは次のルールを設けました: 「evil.combank.comのAPIを呼び出そうとする時、私(ブラウザ)はまずbank.comに尋ねます:『ねえ、evil.comからのアクセスを許可する?』」 もしbank.comAccess-Control-Allow-Origin: *を返さない、またはevil.comをホワイトリストに登録していない場合、ブラウザはこのリクエストをブロックし、Consoleにあの赤いエラーを表示します。


2. CORSに遭遇した時の解決策

CORS問題には、APIを自分で書けるかどうかによって3つの主要な解決策があります。

解決策一:自分で書いたバックエンドAPIの場合

最も簡単!バックエンドにCORSヘッダーを追加するだけです。

Next.js API Routeの例:

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // ビジネスロジック
  const data = { message: "Hello World" };

  // CORSヘッダーを追加して返す
  return NextResponse.json(data, {
    headers: {
      'Access-Control-Allow-Origin': '*', // 全ドメインを許可(または特定ドメインを指定)
      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    },
  });
}

解決策二:他人のAPI(バックエンドを変更できない)→ Proxy APIを書く!

これが最も一般的なケースです。例えば政府の公開データを取得したいが、政府サーバーがCORSホワイトリストを設定していない場合、フロントエンドからはどうやってもアクセスできません。

「ブラウザだけがCORSをブロックし、バックエンドはブロックしない」という特性を利用して、独自のバックエンドAPIをプロキシ(中継)として作成します。

フロントエンドは自分のAPIを呼び出し(同じドメインなのでCORS問題なし)、そのAPIが政府のAPIを呼び出し、結果をフロントエンドに返します。

Next.js Proxy API実装 (src/app/api/proxy/route.ts):

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  try {
    // 1. 自社バックエンドから政府APIを呼び出す(CORS問題ゼロ!)
    const response = await fetch('https://government.api.com/data');
    const data = await response.json();

    // 2. 結果をそのままフロントエンドに返す
    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json({ error: "データ取得に失敗しました" }, { status: 500 });
  }
}

フロントエンドではfetch('/api/proxy')と記述するだけで、問題は完璧に解決します!


3. 第二の強敵:Rate Limit(頻度制限)

CORSを解決し、喜び勇んでuseEffectでデータ取得を実装したあなた。 しかし依存配列を誤って記述し、コンポーネントが無限ループに陥り、1秒間に1000回もAPIリクエストを送信してしまいました。

すると再び画面がクラッシュし、今度は次のエラーが:

HTTP 429 Too Many Requests

これが**Rate Limit(頻度制限)**です。

なぜRate Limitが必要なのか?

サーバーリソースには限りがあります。悪意あるハッカーが1秒間に1万回APIを呼び出すループを書いたら、サーバーはすぐにクラッシュしてしまいます(いわゆるDDoS攻撃)。 これを防ぐため、ほとんどの商用API(OpenAI、Stripe、ECPayなど)は「1つのIPアドレスから1分間に最大60回まで」といった制限を設けています。この制限を超えると、サーバーはサービスを拒否し429エラーを返します。


4. 他人のAPIに制限されないための方法

テクニック1:Exponential Backoff(指数関数的バックオフ)を実装

外部APIから429エラーを受け取った時、すぐに再試行してはいけません!「少し待つ」必要があり、しかも「待ち時間を次第に長くする」のがポイントです。

async function fetchWithBackoff(url: string, retries = 3, delay = 1000) {
  try {
    const response = await fetch(url);
    
    // Rate Limitに達した場合
    if (response.status === 429 && retries > 0) {
      console.log(`レート制限!${delay} ms待機して再試行...`);
      // 指定ミリ秒待機
      await new Promise(resolve => setTimeout(resolve, delay));
      // 再帰的に再試行、待機時間は倍増(1秒→2秒→4秒)
      return fetchWithBackoff(url, retries - 1, delay * 2);
    }
    
    return await response.json();
  } catch (error) {
    throw error;
  }
}

// 使用例
const data = await fetchWithBackoff('https://strict-api.com/data');

テクニック2:Debounce(防抖)をフロントエンドに実装

「検索ボックス」を実装する場合、ユーザーが1文字入力する度にAPIを呼び出していたら、すぐに頻度制限に引っかかります。 Debounceを実装し、「ユーザーが入力停止して500ミリ秒経過後」に初めてAPIを呼び出す必要があります。

// ReactでのDebounce実装(setTimeout利用)
import { useState, useEffect } from 'react';

export default function SearchBar() {
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    // タイマーを設定、500ms後にAPI実行
    const timer = setTimeout(() => {
      if (searchTerm) {
        console.log("API呼��出し: ", searchTerm);
        // fetchAPI(searchTerm);
      }
    }, 500);

    // 500ms以内に再入力された場合、前回タイマーをキャンセル
    return () => clearTimeout(timer);
    
  }, [searchTerm]); // searchTerm変更時に発動

  return (
    <input 
      type="text" 
      onChange={(e) => setSearchTerm(e.target.value)} 
      placeholder="キーワード入力..."
    />
  );
}

CORSとRate Limitを理解したあなたは、もう上級API連携エンジニアの思考を手に入れました。今後これらの用語を見ても恐れる必要はありません。最強のProxy中継とBackoff再試行という武器を手に入れたからです!🚀

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!