
特に注意したいのが、HTTPステータスコードの扱いだ。サーバーが500を返しているのに、ブラウザ側では通信そのものが成功したように見える。この挙動を理解しないままFetch APIを使うと、画面には成功メッセージが表示され、裏側では処理が失敗しているという状態が起こり得る。
反対に、Axiosを導入すればすべてが解決するわけでもない。依存関係が増え、標準APIでは自分で設計できる部分までライブラリの流儀に乗ることになる。小さなページでは、その便利さがかえって構成を重くする場合もある。
結局のところ、Fetch APIとAxiosの使い分けの基準は、単純な機能数の比較では決まらない。プロジェクトがどの程度の共通処理を必要とし、どこまで通信処理を一元管理したいのか。そこを見ないまま、流行や好みだけで選ぶと後から移行コストが発生する。
標準APIのFetchとライブラリAxiosの根本的な設計思想
Fetch APIは、ブラウザや実行環境が提供する標準のHTTP通信APIだ。追加のパッケージをインストールしなくても利用でき、ブラウザに近い仕組みをそのまま扱える。
ブラウザ側のコードで使う場合、基本的にはグローバルに用意されたfetchを呼び出す。Node.jsでも近年のバージョンではFetch APIが標準機能として提供されているため、ブラウザとサーバーの両方で同じ方向性のコードを書きやすくなった。
一方のAxiosは、HTTP通信に必要になりやすい処理をまとめたクライアントライブラリだ。リクエストの作成、レスポンスの変換、エラーの分類、共通ヘッダーの設定などを、Fetch APIより少ない記述で組み立てられるように設計されている。
この違いを一言で表すなら、Fetch APIは通信の土台であり、Axiosはアプリケーション向けに整えられた道具だ。
Fetch APIを選ぶと、必要な処理を自分で組み合わせることになる。これは面倒にも見えるが、裏返せば不要な機能を持ち込まずに済むということでもある。通信の仕様を細かく管理したい場合や、依存関係を増やしたくない場合には、標準APIであることがそのまま強みになる。
Axiosは、最初から一定の設計方針を持っている。たとえば、HTTPステータスが2xx以外の場合はエラーとして扱う。レスポンスがJSONであれば、利用しやすい形への変換も任せられる。認証トークンの付与や共通ログの送信なども、インターセプターを使って通信層に集約しやすい。
ただし、Axiosの便利さは、アプリケーション側が必要とする機能と重なっている場合に初めて価値を持つ。単純な問い合わせフォームや、数個のAPIを呼ぶだけのページであれば、Fetch APIに小さなラッパーを用意するだけで十分なことも多い。
「axios 不要」と言えるのはどのようなケースか
Axiosが不要になりやすいのは、次のような構成だ。
- 取得するAPIが少なく、通信処理が一か所か数か所に収まっている
- 認証トークンの更新や再試行をクライアント側で扱わない
- ブラウザの標準機能だけで動くことを優先している
- バンドルサイズや依存パッケージの数をできるだけ抑えたい
- Fetch APIの挙動を理解したメンバーが、共通ラッパーを保守できる
この場合、Axiosを導入しても、使っているのはリクエスト送信とJSON取得だけになりやすい。導入そのものが悪いわけではないが、ライブラリが提供する機能の大部分を使わないなら、依存を増やす理由は弱くなる。
逆に、エンドポイントが増え、複数の画面から同じ認証処理やエラー処理を呼び出すようになると、Axiosの設計が効いてくる。重要なのは、最初から規模だけで決めないことだ。小さく始めたプロジェクトでも、通信処理が膨らむ兆候が見えた時点で、構成を見直せばよい。
エラーハンドリングとJSONパースの挙動における決定的な違い
Fetch APIとAxiosの違いを語るとき、最初に確認すべきなのがPromiseの扱いだ。
Fetch APIは、サーバーからHTTPレスポンスを受け取れた場合、ステータスコードが404や500であっても、基本的にはPromiseをRejectしない。ネットワーク接続が失敗した、名前解決ができなかった、CORSの制約でレスポンスを利用できなかった、といった通信そのものの失敗とは別に、HTTPエラーを扱う必要がある。
つまり、次の二つはFetch APIにとって別の出来事だ。
1. サーバーへ到達できず、レスポンスを受け取れない
2. サーバーへ到達し、500や422などのレスポンスを受け取る
前者はcatchで処理しやすい。後者はレスポンスを受け取れているため、catchだけでは捕捉できない。response.okやresponse.statusを確認しなければ、後続処理に進んでしまう可能性がある。
管理画面で起こりがちなのは、Fetch APIでレスポンスを受け取ったあと、すぐにresponse.json()を呼び出し、取得したデータが存在する前提で画面を更新するパターンだ。APIが500を返していても、エラー状態を確認しないままJSONの内容を処理すると、画面側では原因の分からない表示崩れにつながる。
この挙動は、Fetch APIにエラーハンドリング機能がないという意味ではない。正確には、HTTPステータスの判定を自動では強制しないということだ。標準APIとしては、サーバーからレスポンスが返ってきた以上、通信は成立したと判断する。その先をアプリケーションの責任として残している。
Axiosでは、標準設定のまま使う場合、2xx以外のレスポンスがエラーとして扱われる。したがって、HTTPエラーをcatch側に集約しやすい。サーバーが422を返した場合は入力エラー、401なら認証切れ、403なら権限不足、500ならサーバー側の問題というように、エラーの入口を統一できる。
ネットワークにつながっていても、サーバーが失敗を返しているなら、アプリケーションにとっては失敗である。その判定を毎回書くのか、通信層で共通化するのかが、Fetch APIとAxiosを分ける最初の基準になる。
JSONパースも「自動」と「明示」の差がある
JSONの扱いにも違いがある。
Fetch APIでは、レスポンスオブジェクトを受け取ったあとにresponse.json()を呼ぶ必要がある。この処理自体も非同期なので、通信処理とJSONパースの二段階を意識しなければならない。
さらに、レスポンスが必ずJSONであるとは限らない。204のようにボディを持たないレスポンス、空のレスポンス、エラーページのHTML、形式が壊れたJSONなどが返る可能性もある。すべてのレスポンスに対して無条件でjson()を呼べば、パースエラーが発生する。
Axiosでは、レスポンスの内容に応じてJSONデータを扱いやすい形へ変換してくれる。呼び出し側は多くの場合、response.dataにアクセスすればよい。記述量が減るだけでなく、各コンポーネントがパース処理を意識しなくて済む。
ただし、Axiosでもレスポンス形式を確認しなくてよいわけではない。JSONを返す契約だったAPIがHTMLを返したり、バックエンドの例外処理が想定外の形式になったりすれば、クライアント側での扱いは難しくなる。自動変換は便利だが、APIの契約を曖昧にしてよい理由にはならない。
Fetch APIで共通化するなら、最初に決めること
Fetch APIを使い続ける場合は、各コンポーネントで個別に処理を書くのではなく、早い段階で通信の入口をまとめるとよい。
共通ラッパーに持たせる候補は、少なくとも次のようなものだ。
- HTTPメソッド、ベースURL、共通ヘッダーの設定
- 認証トークンの付与
response.okを使ったステータス判定- JSON、空レスポンス、テキストレスポンスの切り分け
- APIエラーの形式をアプリケーション内のエラー型へ変換
- ネットワークエラーとHTTPエラーの分類
- 必要に応じたログ送信やリトライ
ここで大切なのは、Axiosの見た目をそのまま再現しようとしないことだ。Fetch APIを使う理由が依存の削減であるなら、必要な範囲だけを薄く包めばよい。機能を足し続けた結果、独自のHTTPライブラリを作り始めると、今度はその保守が負担になる。
インターセプター機能がもたらす大規模開発での保守性
APIの数が増えたときに問題になるのは、リクエストそのものよりも、毎回繰り返される周辺処理だ。
認証トークンを付ける。特定のヘッダーを送る。レスポンスの形式を確認する。401が返ったらログイン状態を確認する。エラーを監視基盤へ送る。こうした処理を各コンポーネントに書き始めると、画面の責務が曖昧になる。
Axiosには、リクエストの送信前とレスポンスの受信後に処理を差し込めるインターセプターがある。これにより、APIを呼び出す画面側は、データを要求して結果を表示することに集中しやすくなる。
リクエスト側で扱いやすい共通処理
リクエストインターセプターでは、たとえば次のような処理をまとめられる。
Authorizationヘッダーへのアクセストークン設定- CSRF対策に必要な情報の付与
- リクエストIDやクライアント情報の追加
- 特定のAPIだけに必要な共通ヘッダーの設定
- 開発環境でのリクエストログ出力
LaravelのAPIをVueから呼び出す構成では、認証方式によって必要な設定が変わる。セッションベースの認証ならCookieやCSRFトークンの扱いが中心になるし、トークンベースなら有効期限と更新処理が重要になる。いずれにしても、各コンポーネントが認証の詳細を知っている状態は長く続けないほうがよい。
レスポンス側で扱いやすい共通処理
レスポンスインターセプターでは、エラー処理やデータの変換を集約できる。
401を受け取ったらログイン画面へ誘導する。403なら権限不足として表示する。422ならバリデーションエラーとしてフォームへ返す。500なら利用者向けの一般的なエラー表示と、開発者向けのログ送信を分ける。このような振り分けを、画面ごとに繰り返す必要がなくなる。
ただし、401の処理は特に慎重に設計したい。アクセストークンの更新処理を自動化する場合、同時に複数のリクエストが失敗すると、更新処理が重複して実行されることがある。更新中のリクエストを待機させる仕組みや、更新失敗時に無限ループしないための条件が必要になる。
インターセプターに何でも詰め込むと、今度は通信層がブラックボックス化する。どのAPIでも必ず同じ処理をするのか、特定のAPIだけ例外があるのかを明確にしなければならない。共通化は重複を減らすための手段であって、例外を隠すためのものではない。
Fetch APIにも同じ設計は持ち込める
Fetch APIにインターセプターという名前の機能はない。しかし、同じ目的をラッパー関数や独自クライアントで実現することはできる。
たとえば、認証情報を付ける関数、レスポンスを検証する関数、エラーを変換する関数を順番に呼び出す設計にすれば、共通処理を通信層へ寄せられる。必要であれば、リクエストとレスポンスの前後にフックを設けることもできる。
違いは、Axiosなら既存の仕組みを利用できるのに対し、Fetch APIでは設計と保守を自分で引き受ける点だ。小規模なアプリケーションなら問題にならないが、チーム開発では独自ラッパーの仕様を理解するためのコストも発生する。
バンドルサイズとNode.js環境でのネイティブサポート状況
Fetch APIとAxiosを比較するとき、機能差ばかりに目が向きがちだが、配信するJavaScriptの量と実行環境も判断材料になる。
Fetch APIは標準機能なので、ブラウザ向けの追加ライブラリを導入する必要がない。特に、JavaScriptの量を抑えたいLPや、最初の表示速度を重視するページでは、この差が分かりやすい。
一方、すでに大きなSPAを配信しているなら、Axiosの追加分だけを見て結論を出すのは早い。ソースマップを除いた本番バンドルのサイズ、圧縮後の転送量、キャッシュの状態、初期ロード時にそのコードが必要かどうかまで確認する必要がある。
管理画面のようにログイン後にだけ読み込まれるチャンクへAxiosを分離できるなら、公開ページのパフォーマンスには影響しない場合もある。逆に、全ページで共通の依存として読み込まれる構成なら、軽量ページにも通信ライブラリの分が乗る。
Node.jsでは実行対象のバージョンを先に決める
Node.jsでは、近年のバージョンでFetch APIが標準的に利用できるようになった。SSRやBFF、ビルド時のデータ取得など、サーバー側でもHTTP通信を行う構成では、ブラウザとNode.jsで同じAPIを使えることが利点になる。
ただし、「Node.jsなら必ずFetch APIが使える」と考えるのは危険だ。プロジェクトの実行環境がどのバージョンなのか、開発環境と本番環境が一致しているのか、テストランナーがどのAPIを提供しているのかを確認する必要がある。
古いNode.jsをサポートする場合は、Fetch APIのポリフィルや別のHTTPクライアントが必要になることがある。ポリフィルを入れると、標準APIだけで完結するという前提は崩れる。サーバーサイドでのTLS、タイムアウト、ストリーム、プロキシ設定なども、実行環境によって挙動を確認したい部分だ。
LaravelとVueの組み合わせでも、通信がブラウザだけで完結するとは限らない。Laravelから外部APIを呼ぶ場合、VueのSSRを使う場合、ビルド時にCMSのデータを取得する場合など、同じプロジェクト内に複数のHTTP通信環境が存在することがある。
WordPressをヘッドレスCMSとして使う構成でも同じだ。公開ページではFetch APIを使い、管理用のNode.js処理ではAxiosを使う、といった分散は技術的には可能だが、エラー形式や認証の考え方がバラバラになりやすい。ブラウザとサーバーで同じ設計を共有したいなら、ライブラリの統一よりも、エラー形式やAPIクライアントの責務をそろえることが重要になる。
プロジェクト規模と要件に応じた技術選定の判断基準
Fetch APIとAxiosの違いを、実際の選定に落とし込むと次のようになる。
| 判断軸 | Fetch APIが向いているケース | Axiosが向いているケース |
|---|---|---|
| 依存関係 | 標準APIを優先し、追加パッケージを減らしたい | 通信処理のためのライブラリ導入を許容できる |
| HTTPエラー | response.okやステータス判定を共通ラッパーで管理できる | 非2xxをエラーとして扱い、catchへ集約したい |
| JSON処理 | レスポンス形式を自分で細かく制御したい | JSONレスポンスを手早く利用したい |
| 認証 | 認証処理が単純で、対象エンドポイントも少ない | トークン付与、更新、ログアウト処理を一元化したい |
| 共通処理 | 小さなラッパーを自分たちで保守できる | インターセプターを使って重複を減らしたい |
| 実行環境 | Fetch APIが利用できるブラウザとNode.jsに限定できる | 複数環境の差をライブラリ側で吸収したい |
| バンドル | 公開ページをできるだけ軽くしたい | 追加サイズより開発・運用上の利便性を優先したい |
| チーム | Web標準の挙動を理解して実装できる | メンバー間で決まったHTTPクライアントを共有したい |
この表で一番見落としやすいのは、エラー処理の一貫性だ。通信が成功したかどうかだけでなく、アプリケーションが正常なデータを受け取れたかまで定義しなければならない。
たとえば、Laravelのバリデーションエラーは422、未認証は401、権限不足は403、サーバー例外は500というように、API側のレスポンスを一定のルールで設計する。そのうえで、フロントエンドのHTTPクライアントが各状態をどのエラー型へ変換するかを決める。ここまで整理できていれば、Fetch APIでもAxiosでも、画面側のコードはかなり読みやすくなる。
反対に、APIのレスポンス形式がエンドポイントごとに異なり、HTTPクライアントも画面ごとに別々だと、Axiosを導入しても混乱は解消しない。ライブラリは設計の代わりにはならない。
依存を減らしたいのか、通信処理の重複を減らしたいのか。優先順位が違えば、同じプロジェクトでも選ぶべきHTTPクライアントは変わる。
Fetch APIを選ぶときに決めておきたいこと
Fetch APIで始めるなら、次のようなルールを先に決めると、後からAxiosへ移行する必要性も判断しやすい。
まず、HTTPステータスの判定方法を統一する。各コンポーネントでresponse.okを確認するのではなく、共通関数の中で判定し、エラーを一つの形式に変換する。
次に、レスポンスのパース方法を決める。JSONだけを扱うのか、ファイルのダウンロードや空レスポンスも扱うのかで、共通関数の設計は変わる。JSONしか返さないAPIなら単純化できるが、将来ファイルを扱う可能性があるなら、最初からレスポンス形式を固定しすぎないほうがよい。
タイムアウトも忘れやすい。Fetch APIには、Axiosのように設定値を指定するだけのタイムアウト処理はない。AbortControllerを使い、一定時間を超えたリクエストを中断する仕組みを用意する必要がある。
リトライも同じだ。すべてのエラーを再試行してよいわけではない。GETのように再実行の影響が比較的小さい処理と、決済や登録のように重複実行が問題になる処理では扱いを分けるべきだ。Axiosでも自動リトライは別途設計が必要なので、ここはライブラリを選べば解決する問題ではない。
Axiosを選ぶときに気を付けたいこと
Axiosを導入するなら、まずインスタンスを作り、各画面が直接設定を重ねない形にするのが基本だ。ベースURL、共通ヘッダー、認証情報、エラー処理を一か所に置くことで、導入の効果が出る。
ただし、すべての通信を一つのインスタンスに詰め込む必要はない。外部サービス、Laravelの内部API、WordPressのAPIなど、認証方式やタイムアウトが違う通信は、別のクライアントとして分けたほうが安全なことがある。
また、Axiosのエラーオブジェクトをそのまま画面へ渡す設計も避けたい。Axios固有の構造にコンポーネントが依存すると、あとでFetch APIへ戻すときだけでなく、テストやサーバー側への処理移動でも足かせになる。通信層でアプリケーション固有のエラーへ変換し、画面はそのエラーだけを見る設計にしておくと、選択肢を残せる。
移行するなら、ライブラリの置き換えより境界の整理から始める
Fetch APIからAxiosへ移行する場合、呼び出し箇所を機械的に置き換えるだけでは不十分だ。
先に確認したいのは、現在の通信処理がどこに散らばっているかである。Vueコンポーネント、Store、Composable、ユーティリティ、認証用モジュールなどに同じ処理が分散しているなら、Axiosへの変更を機に通信の境界を整理したほうがよい。
移行の順番は、次のように考えると進めやすい。
1. APIごとに成功時と失敗時のレスポンス形式を確認する
2. HTTPエラーとネットワークエラーを分けて扱う
3. 画面側に散らばった認証やヘッダー処理を洗い出す
4. 通信クライアントを経由する入口を作る
5. 影響の小さいAPIから新しいクライアントへ切り替える
6. 401や422、500、タイムアウト時の表示を確認する
7. 古い通信処理を削除し、二重の実装を残さない
この順番なら、ライブラリを替えた結果としてエラー処理の意味まで変わってしまう事故を減らせる。
逆に、Fetch APIをAxios風の独自ラッパーへ置き換える場合も同じだ。目的が「Axiosを使わないこと」になってしまうと、必要以上に多機能なクライアントを作ることになる。必要なのは、プロジェクトの要件に合わせた通信の境界であって、ライブラリの完全な再現ではない。
Laravel、Vue、WordPressで考える現実的な使い分け
LaravelとVueの管理画面では、認証済みユーザーの操作が多く、422や401、403といった状態を画面上で正しく扱う必要がある。複数のAPIから同じ認証情報を送り、共通のエラー表示やログ送信を行うなら、Axiosのインスタンスとインターセプターは候補になりやすい。
一方、WordPressの記事一覧を公開ページに表示するだけなら、Fetch APIで十分な場合がある。取得処理が単純で、認証もなく、レスポンスを表示するだけなら、追加ライブラリの導入による利点は限定的だ。
BFFをNode.jsで用意する場合は、サーバー側のNode.jsバージョンと、外部APIの要件を確認する。Fetch APIで統一するなら、タイムアウト、再試行、レスポンス変換、ログ出力を自分たちで設計することになる。Axiosなら実装の初速は上げやすいが、サーバー側で必要なプロキシやストリーム処理まで自動的に片付くわけではない。
実際のプロジェクトでは、すべてをFetch APIかAxiosのどちらか一つに統一しなければならないとは限らない。公開ページではFetch API、認証が必要な管理画面ではAxiosという分け方も成立する。
ただし、使い分けをするなら、呼び出し側が違いを意識しすぎないようにしたい。エラーの型、認証切れの扱い、データの受け渡し方まで別々になると、同じプロジェクト内で学習コストが増える。ライブラリを分けても、アプリケーションの境界はそろえる。この順番が重要だ。
最終的な選択は「今の複雑さ」に合わせる
Fetch APIとAxiosは、どちらが常に優れているという関係ではない。
Fetch APIは標準であり、依存が少なく、仕組みを自分で把握しやすい。その代わり、HTTPステータスの判定、JSONパース、タイムアウト、認証、エラー形式の統一を自分で設計する必要がある。小規模なページや単純なAPI連携では、その自由度が扱いやすさにつながる。
Axiosは、HTTP通信で繰り返し発生する処理をまとめやすい。非2xxの扱い、JSONレスポンス、インターセプター、インスタンス管理など、アプリケーションが大きくなるほど便利さを感じやすい。ただし、依存の追加とライブラリ固有の仕様を受け入れることになる。
判断に迷ったときは、次の順番で考えればよい。
- APIの数と通信処理の重複はどの程度あるか
- 401、403、422、500を画面でどう扱うか
- 認証トークンの付与や更新をどこで管理するか
- ブラウザ以外の実行環境でも同じ通信処理を使うか
- バンドルサイズと依存パッケージの削減は本当に優先事項か
- Fetch APIの共通ラッパーを誰が保守するのか
- 将来の移行を考えたとき、画面が特定ライブラリに依存していないか
個人開発や小さなサービスなら、まずFetch APIで始めてもよい。response.okの確認とエラー変換を共通化しておけば、後から困る可能性はかなり下げられる。
反対に、認証付きの管理画面でAPIが増え続けているなら、Axiosを導入する理由は明確になる。インターセプターを使って共通処理を集約し、コンポーネントから通信の細部を切り離せるからだ。
大切なのは、Fetch APIを使っていることでも、Axiosを導入していることでもない。サーバーが返した失敗を正しく失敗として扱い、認証やエラー処理の責務を適切な場所に置くことだ。
自分のプロダクトで選ぶべきなのは、最も機能が多い道具ではない。現在の構成に対して、最も重複を減らし、最も挙動を説明しやすく、将来の変更を邪魔しない道具である。Fetch APIとAxiosの使い分けの基準は、そこにある。
Related reading: Laravel SanctumとPassport:API認証における選定基準の最適解.