API 設計と fetch の関係の全体像
fetch 自体は「URL に HTTP リクエストを送って、レスポンスを受け取るだけ」の素朴な道具です。
でも、実際のコードの書きやすさ・分かりやすさは、API の設計が良いかどうかにかなり左右されます。
同じ fetch でも、API がこうだと楽です。
- わかりやすい URL と HTTP メソッド
- 一貫した JSON 形式
- 分かりやすいステータスコードとエラー形式
- 認証方法がシンプル
逆に、API がバラバラなルールで設計されていると、フロント側では if 文と例外処理だらけになり、初心者が見ても「何をしているのか」が全然分からなくなります。
ここが重要です。
fetch の書き方だけを覚えるのではなく、「どんな API 設計だと、fetch をシンプルに書けるのか?」という視点を持つと、一気に理解が深くなります。
エンドポイント設計と fetch の書きやすさ
リソースと URL 設計
API はよく「ユーザー」「記事」「商品」のような「リソース」単位で考えます。
例として「ユーザー一覧を取得する API」を考えます。
良い設計の例は、だいたいこんな感じです。
GET /api/users
これだと、フロント側の fetch はとても素直に書けます。
const response = await fetch("/api/users");
const users = await response.json();
JavaScriptユーザー詳細なら、
GET /api/users/123
で、
const response = await fetch("/api/users/123");
const user = await response.json();
JavaScriptこのように「URLの形から、何をしているかが想像できる」API は、fetch のコードを見たときにも意味がすぐ分かります。
もしこうだったら、どうでしょう。
POST /api/getUserList
POST /api/fetchUserDetailById
リソースではなく、「操作名っぽいもの」が乱立していると、フロント側のコードも読みにくくなります。
await fetch("/api/getUserList", { method: "POST" });
await fetch("/api/fetchUserDetailById", { method: "POST", body: ... });
JavaScript「これは何をしている URL なんだっけ?」と毎回読み解かないといけません。
ここが重要です。
URL とメソッドの設計(API 設計)が素直だと、fetch を書くときに「意図」がそのままコードに現れるようになります。
逆に API が分かりにくいと、fetch をいくら頑張ってもコードが意味不明になりがちです。
HTTP メソッドと意味の対応
良い API 設計では、HTTP メソッドの意味が統一されています。
例えばユーザーに対して:
- GET
/api/users… 一覧取得 - GET
/api/users/123… 1件取得 - POST
/api/users… 新規作成 - PUT
/api/users/123… 全体更新 - PATCH
/api/users/123… 部分更新 - DELETE
/api/users/123… 削除
こう決まっていると、フロント側の fetch もパターン化できます。
// 一覧
await fetch("/api/users");
// 作成
await fetch("/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newUser),
});
// 削除
await fetch("/api/users/123", { method: "DELETE" });
JavaScriptここが重要です。
API 設計で HTTP メソッドの意味が揃っていれば、
「メソッドを見ただけで何をしたいか分かる fetch」が書けます。
これは、後から自分や他人がコードを読むときの理解コストを大きく下げてくれます。
レスポンス設計(JSON とステータスコード)と fetch
一貫した JSON 形式は「フロントの救い」
API から返ってくる JSON の形式がバラバラだと、
フロントのコードは if / try-catch だらけになります。
良い例として、「成功時はこう」「エラー時はこう」と形を揃えているケースを考えます。
例えば、成功時は必ずこんな形だとします。
{
"success": true,
"data": { "id": 1, "name": "太郎" }
}
エラー時はこう。
{
"success": false,
"error": { "code": "VALIDATION_ERROR", "message": "名前は必須です" }
}
この前提があれば、フロント側はいつも同じパターンで処理できます。
const response = await fetch("/api/user/1");
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
const body = await response.json();
if (!body.success) {
console.error("アプリケーションエラー:", body.error.message);
return;
}
console.log("ユーザー:", body.data);
JavaScriptAPI 設計がこう決めてくれていれば、
どのエンドポイントでも同じように書けるので、初心者でもパターンを覚えやすくなります。
ステータスコードと fetch の関係
API 設計側で、ステータスコードの使い方も揃えておくとさらに楽になります。
例えば:
- 成功時は 200 / 201 など成功系のステータス
- パラメータ不正のときは 400
- 認証エラーは 401
- 権限がないときは 403
- その ID のデータがないときは 404
- サーバー側のバグや不調は 500
と統一されていると、フロント側はこう書けます。
const response = await fetch("/api/profile");
if (response.status === 401) {
// ログイン画面へ誘導
} else if (response.status === 404) {
// 「プロフィールがまだ設定されていません」と表示
} else if (!response.ok) {
// その他のエラー(500など)
}
JavaScriptここが重要です。
API 設計でステータスコードの意味付けがちゃんとしていると、
fetch 側は「status / ok を見るだけで、どう振る舞うか決められる」ようになります。
逆に API が 200 しか返さず、エラーを全部 JSON の中の謎フラグで表現すると、フロント側が苦しみます。
認証・認可の設計と fetch
認証のやり方が統一されているとヘッダーがシンプルになる
API によっては、「このヘッダーでトークンを送ってください」と決められています。
代表的なのが Authorization ヘッダーです。
例えば API 設計が、
「全ての保護されたエンドポイントは、Authorization: Bearer <token> を受け取る」
と決めてくれると、フロント側はパターン化できます。
async function fetchWithAuth(url, token, options = {}) {
const headers = {
...(options.headers || {}),
Authorization: `Bearer ${token}`,
};
const response = await fetch(url, {
...options,
headers,
});
return response;
}
JavaScriptどの API でも、これを使えば OK です。
API 設計がバラバラで、
- あるエンドポイントは
X-Auth-Tokenヘッダー - 別のエンドポイントはクエリパラメータ
?token= - さらに別のは body の中に
tokenフィールド
みたいなことになっていると、フロント側は毎回書き分けないといけません。
認証失敗時のルールが揃っていると、処理も揃う
API 側が「認証エラーは必ず 401」を返すと決めてくれれば、
フロント側はどの fetch でも同じように処理できます。
const response = await fetchWithAuth("/api/profile", token);
if (response.status === 401) {
redirectToLogin();
return;
}
JavaScript設計が良くないと、「このエンドポイントは 403 を返す」「こっちは 200 で body.errorCode を見る」など、エンドポイントごとに条件分岐が増えます。
ここが重要です。
認証・認可の設計が揃っている API だと、
「認証付き fetch」を 1 つ用意するだけで、アプリ全体をカバーできます。
これはフロントエンド実装にとって、ものすごく大きな恩恵です。
ページネーションや検索など、クエリ設計と fetch
ページングの API 設計とフロントコード
ユーザー一覧をページングで取る API を考えてみます。
API 設計がこんな感じだとします。
GET /api/users?page=2&limit=10
レスポンスはこう。
{
"data": [
{ "id": 11, "name": "ユーザー11" },
{ "id": 12, "name": "ユーザー12" }
],
"page": 2,
"limit": 10,
"total": 47
}
このルールが決まっていれば、フロント側はシンプルに書けます。
async function loadUsers(page = 1, limit = 10) {
const params = new URLSearchParams({ page, limit });
const response = await fetch(`/api/users?${params.toString()}`);
if (!response.ok) {
throw new Error("HTTP エラー: " + response.status);
}
const body = await response.json();
return body;
}
JavaScriptAPI 設計が
「あるエンドポイントは page / limit、
別のエンドポイントは offset / size」
とバラバラだと、その分だけ fetch の書き分けが必要になり、コードが散らかります。
検索条件の設計と fetch の見通し
例えば、ユーザー検索 API を考えます。
API 設計で、「検索条件はすべてクエリパラメータで受け取る」と決めていれば、
GET /api/users?keyword=taro&role=admin
という形で呼び出せます。
fetch 側も素直です。
const params = new URLSearchParams({
keyword: "taro",
role: "admin",
});
const response = await fetch(`/api/users?${params.toString()}`);
JavaScriptもし API 設計が、
- この検索は POST + JSON
- あの検索は GET + クエリ
- 別の検索はなぜかパス
/api/users/search/admin/taro
などバラバラだと、フロント側が全部覚えておかないといけません。
ここが重要です。
API 側で「検索条件はこう表現する」「ページングはこう」と揃えてくれると、
fetch のコードもパターン化されて、初心者でも見通しがよくなります。
小さな「良い API 設計」と fetch コードのセットの例
API 側の設計(想定)
ユーザー関係の API を、こう設計したと仮定します。
ユーザー一覧取得
GET /api/users?page=1&limit=10
ユーザー詳細取得
GET /api/users/:id
ユーザー作成
POST /api/users
ボディ JSON、成功時 201 Created、ボディは { success: true, data: {...} }
エラー時(共通)
HTTP ステータスは 4xx / 5xx、ボディはこう。
{
"success": false,
"error": {
"code": "USER_NOT_FOUND",
"message": "ユーザーが見つかりません"
}
}
それに対応するフロント側の fetch ヘルパー
こういう API 設計なら、フロント側は共通のヘルパーを作れます。
async function callApi(url, options = {}) {
const response = await fetch(url, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
...(options.headers || {}),
},
...options,
});
const body = await response.json().catch(() => null);
if (!response.ok || (body && body.success === false)) {
const message =
body?.error?.message ||
`HTTP エラー: ${response.status}`;
throw new Error(message);
}
return body;
}
JavaScriptこれがあれば、各画面のコードはかなり短くできます。
ユーザー一覧:
async function loadUsers(page = 1, limit = 10) {
const params = new URLSearchParams({ page, limit });
const body = await callApi(`/api/users?${params.toString()}`);
return body.data;
}
JavaScriptユーザー詳細:
async function loadUser(id) {
const body = await callApi(`/api/users/${id}`);
return body.data;
}
JavaScriptユーザー作成:
async function createUser(user) {
const body = await callApi("/api/users", {
method: "POST",
body: JSON.stringify(user),
});
return body.data;
}
JavaScriptここが重要です。
API 設計が「URL・メソッド・JSON形式・エラー形式」を揃えてくれていると、
フロント側は共通の fetch ラッパーを 1 個作るだけで、ほぼ全部の通信パターンをカバーできます。
これは、初心者にとっても「覚えるべき形が少なくて済む」という意味で大きなメリットです。
初心者として「API 設計との関係」で本当に押さえてほしいこと
fetch の書き方だけを見ても、実際のアプリではすぐ限界がきます。
なぜなら、fetch は「API 設計の良し悪し」をそのまま受け止める鏡だからです。
URL と HTTP メソッドがリソース指向で分かりやすく設計されていると、
fetch のコードも「見ただけで意図が分かる」ものになります。
レスポンスの JSON 形式やステータスコードの使い方が統一されていると、
「fetch → ok チェック → json → data / error を見る」パターンだけで、ほとんどの通信を扱えるようになります。
認証・ページング・検索など API のルールが揃っていると、
フロント側は共通のヘルパー関数を作りやすくなり、
画面ごとのコードは「何をしたいか」だけに集中できるようになります。
ここが重要です。
fetch の練習をするとき、
「この API の設計は、フロントから見て分かりやすいか?」
「自分なら、レスポンスやエラーをどういう形に設計するか?」
という視点も一緒に持ってみてください。
“ただ叩くだけの人” から、“会話の相手(API)の設計も意識できる人” に変わると、
コードの質も、チームで話せる内容も、一段レベルアップします。

