😈 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など)」でfetchやaxiosを使って**「異なるドメイン」**のAPIを呼び出す時だけ、ブラウザはこの防護盾を発動します。
なぜこの防護盾が必要なのか?
銀行サイトにログインした後、誤って詐欺サイト(evil.com)を開いたと想像してください。
CORSがない場合、この詐欺サイトは背後でJavaScriptを使って銀行のAPIを呼び出せます:fetch('https://bank.com/api/transfer')、そしてあなたの口座からお金を引き出せてしまいます!
これを防ぐため、ブラウザは次のルールを設けました:
「evil.comがbank.comのAPIを呼び出そうとする時、私(ブラウザ)はまずbank.comに尋ねます:『ねえ、evil.comからのアクセスを許可する?』」
もしbank.comがAccess-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再試行という武器を手に入れたからです!🚀