記事一覧へ戻る
読了 約 5

cURL から fetch / axios への移行で詰まる 7 つのポイント

#development

執筆: デジタル道具屋 編集部 / API 連携担当

BtoB SaaS の API 連携プロジェクトで、サードパーティ API への接続実装を多数担当する編集チームです。

本記事の執筆方針と編集ポリシーについてはAbout ページをご覧ください。

導入:「ドキュメントの cURL は動くのに、JS から呼ぶと 401」

男の子
男の子
「博士、API のドキュメントに載ってる cURL コマンドは動くんだけど、それを fetch に書き換えたら 401 になるんだぜ! 同じヘッダー渡してるのに何でだぜ?」
博士
博士
「ふむ、それは典型的な落とし穴じゃ。cURL と fetch ではデフォルト挙動が違うので、cURL のオプションを 1:1 で『書かない』選択をすると、暗黙の差で動作が変わる。よくあるパターンを順に見ていこう。」

落とし穴 1:-d は「自動的に POST + form 形式」

cURL の -d オプションは 2つの暗黙の振る舞いがあります。

  • HTTP メソッドを 自動的に POST に変える
  • Content-Type: application/x-www-form-urlencoded自動付与する

fetch ではこれを明示する必要があります:

# cURL
curl -d 'name=alice&age=20' https://api.example.com/users

// 等価な fetch
fetch('https://api.example.com/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({ name: 'alice', age: '20' }),
})
女の子
女の子
「JSON を送りたいなら、Content-Type: application/jsonJSON.stringify(...) をセットで指定するのを忘れずに。」

落とし穴 2:Cookie の自動送信

cURL は同一セッション内では Cookie を自動で扱います。fetch は明示的に credentials: 'include' を指定しないと Cookie を送りません。 認証 Cookie を使う API では、これを忘れると 401 / 403 になります。

fetch('https://api.example.com/me', {
  credentials: 'include', // または 'same-origin'
})

さらにブラウザ環境では、サーバー側で Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin に具体的なオリジン(* ではダメ)を返さないと、Cookie 送信が拒否されます。

落とし穴 3:CORS は cURL では起きない

男の子
男の子
「cURL では動くけど fetch でエラーになる…これは CORS なのかよ?」
博士
博士
「その通り。cURL はブラウザのセキュリティ機構を経由しないため、CORS は適用されない。クロスオリジンで API を叩く場合、ブラウザのみで遭遇する問題は CORS と Cookie の SameSite 属性じゃ。」

対策は基本的にサーバー側で適切な Access-Control-* ヘッダーを返すこと。 自分が API を握っていない場合は、Next.js の API ルートやプロキシを挟んで自サーバー経由で呼ぶ手もあります。

落とし穴 4:リダイレクトの扱い

cURL で -L を付けないとリダイレクトを追わないのに対し、fetch はデフォルトで redirect: 'follow'(自動追従)です。 逆に「最初のリクエストだけ送ってリダイレクトの Location ヘッダーを取りたい」場合は、fetch 側で redirect: 'manual' を指定します。

落とし穴 5:HTTPメソッドと body の組み合わせ

GET リクエストに body を付けると、fetch は TypeError になります。 cURL は -X GET -d '...' でも黙って受け付けてしまうため、API ドキュメントが間違って GET + body で書かれているケースがあります。 その場合はクエリパラメータに変更するか、サーバー実装を確認しましょう。

落とし穴 6:レスポンスの読み方

fetch のレスポンスは 1度しか読めませんresponse.json() を呼んだ後に response.text() を呼ぶとエラーです。 生のテキストとパース後の両方が欲しい場合は、const text = await response.text(); const data = JSON.parse(text); と段階を分けます。

// 失敗するパターン
const data = await response.json();
const text = await response.text(); // → エラー

// 正しいパターン
const text = await response.text();
const data = JSON.parse(text);

落とし穴 7:ステータスコードはエラーにならない

cURL は終了コードで成功/失敗を判別しますが、fetch は HTTP レベルで通信できれば 4xx / 5xx でも resolve します。try/catch だけではサーバーエラーを捕捉できないので、response.ok を必ずチェックしましょう。

const res = await fetch(url);
if (!res.ok) {
  // 4xx / 5xx の処理
  throw new Error(`API error: ${res.status}`);
}
const data = await res.json();
女の子
女の子
「axios はこの点が違って、デフォルトで 4xx/5xx を例外にしてくれるわ。チーム内で挙動を揃えるために、fetch でも自前で同じガードを入れる派が多いわね。」

変換作業を機械化する

オプションが多い cURL コマンドを手作業で fetch / axios に書き換えるのは事故のもとです。 当サイトのcURL → Fetch 変換ツールでは、 cURL コマンドを貼り付けるだけで JavaScript の fetch / axios コードに変換できます。 生成されたコードに、本記事で解説した credentialsresponse.ok チェック・エラーハンドリングを足して仕上げてください。

関連: HTTP ステータス検索JSON 整形(API レスポンスのデバッグに)、URL エンコード/デコード

🚨 現場の失敗あるある

AbortController を渡し忘れて、画面遷移後もバックグラウンドで fetch が走り続け、 アンマウント済みコンポーネントへの setState 警告 → メモリリーク、というパターンが React 系で頻発します。 React Query や SWR を使えばこの辺は自動で面倒を見てくれるので、複数の fetch を扱うなら検討する価値があります。

参考にした一次情報

本記事の内容は、以下の公式仕様や一次情報を参照して執筆しています。

この記事の内容を実際に試す

解説した内容は、以下のツールでブラウザ上から無料で試せます。