はじめに
fetch は「Web API と通信するための標準手段」です。
現代の Web アプリケーションは、サーバーやクラウド上の Web API と通信してデータを取得・送信する ことが前提になっています。
- ユーザー情報を取得する
- 投稿一覧を読み込む
- ログイン処理を行う
- 商品データを検索する
- サーバーにデータを保存する
こうした処理はすべて Web API へのアクセス です。
そして Web API の多くは、データの受け渡しに JSON を使う のが一般的です。
JSON が使われる理由は、
- 軽量で読み書きしやすい
- JavaScript と相性が良い(オブジェクトとして扱える)
- ほぼすべての言語・フレームワークが対応している
つまり、Web API の世界では JSON が事実上の標準フォーマット になっているからです。
ブラウザの Web API アクセスの進化
昔は、Web API にアクセスするために XMLHttpRequest(XHR) を使っていました。しかし、コードが複雑で扱いにくいという問題がありました。
そこで登場したのが fetch API です。
fetch は、
- シンプル
Promiseベース- JSON と相性が良い
async/awaitと自然につながる
という特徴を持ち、「Web API と通信するための標準インターフェース」 として設計されています。
fetch の基本
fetch は「Web API にリクエストを送る関数」です。
Promise に対応している為、最も基本的な形は、下記のようになります。
fetch("/api/user")
.then(res => {
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status}`);
}
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error(err));
このコードは、「/api/user という Web API にアクセスし、返ってきた JSON を JavaScript のオブジェクトとして扱う」という意味です。
fetch が Promise を返すのには、理由があります。それは、Web API へのアクセスは 時間がかかる処理だからです。
- ネットワーク通信
- サーバー側の処理
- レスポンスの受信
これらはすべて非同期で行われるため、fetch は Promise(未来の値) を返します。
fetch はレスポンスのヘッダと、ボディを読み込むためのストリームを返します。この際、ボディはまだ読み込まれていないため、JSON として扱うには .json() を呼び出してパースする必要があります。
又、fetch には、 HTTP エラー(404, 500)では reject しないという、仕様上の最大の落とし穴が存在します。そのため、必ず res.ok を確認する必要があります。
この書き方が良い理由は、
- HTTP エラー
- ネットワークエラー
- JSON パースエラー
- UI 更新エラー
これらを 1 つの catch にまとめられる からです。
fetch → async / await への自然な流れ
fetch の処理は、Promise チェーンでも書けますが、Web API の処理は複雑になりやすいため、async / await の方が圧倒的に読みやすくなります。
async function loadUser() {
try {
const res = await fetch("/api/user");
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status}`);
}
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
loadUser();
👉 このように、「Web API にアクセス → JSON を読む → UI を更新する」という一連の流れが、同期処理のように自然に書けるので、実務では、こちらが使われることが多いです。
非同期処理の書き方については、過去記事、「非同期処理のモダンな書き方」を参照してください。
POST / PUT / DELETE の基本
fetch は データを取得する GET だけでなく、第二引数にオプションを渡すだけで、POST / PUT / DELETE を簡単に扱えます。
POST
await fetch("/api/user", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Kon" })
});
PUT
await fetch("/api/user/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Updated" })
});
DELETE
await fetch("/api/user/1", { method: "DELETE" });
CORS の基礎
さて、fetch API により、外部サーバーの Web API にアクセスし、データを取得したり、受け渡しできることがわかりました。
しかし、これだけでは、CORS エラーという予期せぬエラーにみまわれることがあります。
その為、fetch API の利用にあたっては、この CORS という仕組みについて理解しておく必要があります。
CORS とは何か?
CORS(Cross-Origin Resource Sharing)は、「この Web API は、どのサイトからのアクセスを許可するか?」をサーバー側がブラウザに伝える仕組みです。
ブラウザは、サーバーが「このオリジンを許可する」と明示しなかった場合に、セキュリティのため通信をブロックします。これが、「 CORS エラー」です。
では、ブラウザは、なにを基準に判断しているのでしょうか? その為に、使用されるのが、オリジンです。
オリジンとは、以下の 3 つの組み合わせのことをいいます。
- プロトコル(http / https)
- ドメイン
- ポート番号
ブラウザは、現在のサイトが、サーバーが許可した、この 3 つのすべてが一致しているかどうかを判断しています。
図解:CORS の仕組み
[ ブラウザ ] -- リクエスト --> [ Web API サーバー ]
↑ |
| |
└-- サーバーが許可ヘッダを返し、-┘
ブラウザが安全性を判断する
ブラウザは、現在の頁の JavaScript が fetch を行う際、Web API サーバーに、問い合わせを行います。
例えば、現在の頁のドメインが https://blog.konkitsune.com であった場合、https://api.example.com が提供する Web API に fetch するとします。
その際、オリジンが違うので、ブラウザは Web API サーバーに「このサイトからアクセスしていい?」と確認します。
その応答として、Web API サーバーが「許可するよ」と返すのが Access-Control-Allow-Origin です。
Access-Control-Allow-Origin: https://blog.konkitsune.com
これはブラウザに対して、「このオリジン(https://blog.konkitsune.com)からのアクセスは OK です」という意味になります。
ブラウザはこれを確認して、初めて JavaScript にレスポンスを渡します。これが、CORS の仕組みです。
注)Access-Control-Allow-Origin: * は便利ですが、Access-Control-Allow-Credentials: true と併用できない為、認証情報(Cookie / Authorization ヘッダ)を伴うリクエストでは使用できません。
CORS エラーの正体
CORS エラーは JavaScript のエラーではありません。ブラウザが安全のためにサーバーが許可していない通信をブロックしているのです。
その為、CORS エラーは、残念ながら、クライアント側では解決できません。
つまり、CORS は 100% Web API サーバー側の設定で決まるということです。
👉 CORS エラーを回避するには、Web API サーバーに、現在のオリジンに対する、許可を与えてもらう必要があります。
ブラウザが Web API サーバーに事前に問い合わせる仕組み
ブラウザは、危険な可能性があるリクエストを送る前に、「このリクエストを送っても安全ですか?」と API サーバーに確認します。
この確認を プリフライト(OPTIONS リクエスト) と呼びます。
では、なぜプリフライトが必要なのでしょうか。
理由は ブラウザのセキュリティモデル(Same-Origin Policy) にあります。
ブラウザは、悪意あるサイトが勝手に他のサイトの API を叩くのを防ぐため、オリジンが違うサイトへの “危険な” リクエストを制限するというルールを持っています。
ブラウザは、以下の条件に当てはまると「これは危険かもしれない」と判断します。
プリフライトが発生する条件
下記に、ブラウザが、プリフライトが発生する条件を示します。
① Content-Type が “特殊” な場合
以下以外の Content-Type を使うとプリフライトが発生します:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
例:
Content-Type: application/json
② カスタムヘッダを送る場合
「安全ではない可能性がある」と判断されます。
例:
X-API-Key: 12345
Authorization: Bearer token
③ GET / POST / HEAD 以外のメソッド
これらは、「データを変更する可能性が高い」ため、プリフライトが必要になります。
- PUT
- DELETE
- PATCH
図解:プリフライトの流れ
① ブラウザ
↓ OPTIONS(確認)
② API サーバー
↓ OK の返事(許可ヘッダ)
③ ブラウザ
↓ 本番のリクエスト(GET/POST/PUT/DELETE)
④ API サーバー
↓ 本番レスポンス
⑤ ブラウザ → JavaScript に渡す
実際の通信例(PUT / JSON の場合)
あなたが次のような fetch を書いたとします:
fetch("https://api.example.com/user/1", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Kon" })
});
ブラウザはこう判断します:
「PUT だし JSON だし、これは危険かもしれない。まずサーバーに確認しよう。」
① ブラウザ → サーバー(OPTIONS)
OPTIONS /user/1 HTTP/1.1
Origin: https://blog.konkitsune.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
② サーバー → ブラウザ(許可の返事)
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://blog.konkitsune.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type
この返事を見てブラウザはこう判断します:
「OK、サーバーが許可してる。本番の PUT リクエストを送っていい。」
③ ブラウザ → サーバー(本番の PUT)
PUT /user/1
Content-Type: application/json
Origin: https://blog.konkitsune.com
{"name":"Kon"}
④ サーバー → ブラウザ(本番レスポンス)
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://blog.konkitsune.com
プリフライトが発生しない(=Simple Request)条件
下記の条件の場合、プリフライトが発生しません。
- メソッドが GET / POST / HEAD のいずれか
- Content-Type が以下のいずれか
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- カスタムヘッダを送らない
プリフライトの本質
プリフライトとは、「このリクエストを送っても安全ですか?」とブラウザがサーバーに事前確認する仕組みであり、サーバーが許可しない限り、本番リクエストは送られないという点が最重要です。
よくある誤解
❌ 「プリフライトは JavaScript 側で制御できる」
→ できません。ブラウザの仕様です。
❌ 「fetch のオプションでプリフライトを回避できる」
→ できません。
❌ 「CORS エラーはサーバーではなくクライアントの問題」
→ 100% サーバー側の設定です。
| 内容 | 説明 |
|---|---|
| プリフライトとは | 本番リクエストの前に送られる “安全確認” |
| 送信方法 | OPTIONS メソッド |
| いつ発生する? | JSON、カスタムヘッダ、PUT/DELETE など |
| 誰が判断する? | ブラウザ |
| 誰が許可する? | サーバー |
| JavaScript で回避できる? | ❌ できない |
「プリフライトはキャッシュされる」という重要な仕様
実務では プリフライトが毎回発生すると遅くなる ため、Web API サーバーは以下のヘッダでキャッシュを設定することができます。
Access-Control-Max-Age: 86400
これは プリフライト結果を 24 時間キャッシュする という意味です。
まとめ
fetchは Web API と通信するための標準手段- JSON は Web API の事実上の標準フォーマット
fetchはPromiseを返し、async/awaitと相性が良い- HTTP エラーは
rejectされないためres.okの判断が必須 - CORS はブラウザのセキュリティ機構で、サーバー側の設定がすべて
async/awaitにより、すべてのエラー処理を 1 箇所で扱える