📖 テーマ設定
🔊 音声設定
1.2
1.0
1.0
▶️ 再生コントロール
🎵 BGM設定
0.3
🔔 効果音設定
0.3

UIからAPIを呼び出す

概要

  • 日程: Day 4 / セッション 3
  • 時間: [10:45-12:00]
  • 形式: 実習
  • ゴール: JavaScript(fetch)でモックAPIを呼び出し、取得した JSON を画面に一覧表示できる。通信を api.js に、描画を app.js に分けた構成を保ったまま実装できる
  • 学習形式: ハンズオン実習(AIペアプログラミング)

導入(5分)

前のセッションで機能側(モックAPI)が動きました。curl で叩けば JSON が返ってきます。

ここで少し考えてみてください。curl がやっていることを、ブラウザの JavaScript にやらせたら何が起きるでしょうか? そう、「画面にデータが出る」。つまりアプリケーションの誕生です。

このセッションは「プレゼンテーションと機能の分離」の実践編です。Day 2 で描いたワイヤーフレームが、API から届く JSON を着て動き出します。ポイントはただ一つ、UI のコードに機能(ビジネスロジック)を混ぜないこと。書きながら何度も確認します。

本編(20分+実習50分)

1. fetch API 3行でHTTPクライアントになる

fetch は、ブラウザに標準搭載された HTTP クライアントです。curl でやったことがそのまま書けます。

const response = await fetch("http://localhost:3000/expenses");
const expenses = await response.json();
console.log(expenses);  // JSONが配列として手に入る
  • fetch(url) がリクエストを送る(既定は GET)
  • response.json() がレスポンスボディを JavaScript のオブジェクトに変換する
  • どちらも時間がかかる処理なので await で待つ

たとえるなら、fetch は「電話をかける」、await は「相手が出るまで待つ」、response.json() は「聞いた内容をメモに起こす」です。電話をかけた瞬間にメモが手に入るわけではない、という順序が重要です。

POST はこう書きます。curl のオプションと1対1で対応していることに注目してください。

await fetch("http://localhost:3000/expenses", {
  method: "POST",                                   // curl の -X POST
  headers: { "Content-Type": "application/json" },  // curl の -H
  body: JSON.stringify({ date: "2026-07-06", categoryId: 1, amount: 620, memo: "コーヒー" })  // curl の -d
});

ここがポイント

  • await を忘れると、データではなく Promise(未来の約束)が手に入りエラーになる。「Promise とコンソールに出たら await 忘れ」と覚える
  • JSON.stringify はオブジェクト→文字列。response.json() は文字列→オブジェクト。向きが逆の相棒

コラム

fetch の前任者は XMLHttpRequest(XHR)です。1999年、マイクロソフトが Outlook Web Access のために IE5 へこっそり入れた機能で、当初はほぼ無名でした。2004〜2005年に Gmail と Google Maps が「ページを再読み込みせずに画面が変わる」体験で世界を驚かせ、この技術に Ajax という名前が付いて大流行します。ただ XHR は名前に XML とあるのに JSON 全盛になり、API も使いにくかったため、2015年にモダンな fetch が標準化されました。名前に歴史あり、です。

2. 呼び出しの流れを図で持つ

UI が API を呼んで画面に出すまでの流れです。この図が頭にあると、エラーが起きたときに「どこで止まったか」を切り分けられます。

sequenceDiagram participant U as 利用者 participant H as index.html(画面) participant A as app.js(描画) participant J as api.js(通信) participant S as モックAPI(JSON Server) U->>H: ページを開く H->>A: 読み込み完了イベント activate A A->>J: fetchExpenses() を呼ぶ activate J J->>S: GET /expenses activate S S-->>J: 200 OK(JSONを返す) deactivate S J-->>A: 支出の配列を返す deactivate J A->>H: DOM を書き換えて一覧表示 deactivate A H-->>U: 支出一覧が見える

登場人物の役割分担がそのまま「分離」です。

  • api.js: 通信だけを担当。画面のことを何も知らない
  • app.js: 描画とイベントだけを担当。URL や HTTP のことを知らない
  • モックAPI: 機能側。UI のことを何も知らない

ここがポイント

  • エラー時は図の矢印を上から順に疑う。「API は生きてるか(curl で確認)→ 通信は届いたか(開発者ツールの Network タブ)→ 描画は呼ばれたか(console.log)」
  • api.js が「窓口の一本化」になっているので、API の URL 変更は api.js の1行を直すだけで済む

コラム

sequenceDiagram のような時系列図のご先祖は、通信プロトコルの世界で使われた「メッセージシーケンスチャート」です。1990年代に UML に取り込まれ、シーケンス図として定着しました。人間同士でも「誰が・誰に・どの順で」話すかを図にすると誤解が減ります。議事録より図1枚、はエンジニアの生活の知恵です。

3. 完全に動くコード例 家計簿の一覧と登録

前セッションの JSON Server(ポート3000)が起動している前提で、そのまま動く一式です。ui/ フォルダに3ファイル作ります。

ui/index.html(プレゼンテーションの構造。ワイヤーフレームの翻訳)

<meta charset="utf-8">
<title>家計簿</title>
<h1>家計簿 支出一覧</h1>

<form id="expense-form">
  <input type="date" id="date" required>
  <select id="categoryId"></select>
  <input type="number" id="amount" placeholder="金額" required min="1">
  <input type="text" id="memo" placeholder="メモ">
  <button type="submit">登録</button>
</form>

<table border="1">
  <thead>
    <tr><th>日付</th><th>カテゴリ</th><th>金額</th><th>メモ</th></tr>
  </thead>
  <tbody id="expense-list"></tbody>
</table>
<p id="total"></p>

<script src="api.js"></script>
<script src="app.js"></script>

ui/api.js(通信の窓口。機能側との契約をここに集約)

const API_BASE = "http://localhost:3000";

async function fetchExpenses() {
  const res = await fetch(`${API_BASE}/expenses`);
  if (!res.ok) throw new Error(`APIエラー: ${res.status}`);
  return res.json();
}

async function fetchCategories() {
  const res = await fetch(`${API_BASE}/categories`);
  if (!res.ok) throw new Error(`APIエラー: ${res.status}`);
  return res.json();
}

async function createExpense(expense) {
  const res = await fetch(`${API_BASE}/expenses`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(expense)
  });
  if (!res.ok) throw new Error(`登録失敗: ${res.status}`);
  return res.json();
}

ui/app.js(描画とイベント。HTTP のことは知らない)

async function renderCategories() {
  const categories = await fetchCategories();
  const select = document.getElementById("categoryId");
  select.innerHTML = categories
    .map((c) => `<option value="${c.id}">${c.name}</option>`)
    .join("");
}

async function renderExpenses() {
  const expenses = await fetchExpenses();
  const categories = await fetchCategories();
  const nameOf = (id) => categories.find((c) => c.id === Number(id))?.name ?? "不明";

  const tbody = document.getElementById("expense-list");
  tbody.innerHTML = expenses
    .map((e) => `<tr>
      <td>${e.date}</td><td>${nameOf(e.categoryId)}</td>
      <td>${e.amount.toLocaleString()}円</td><td>${e.memo ?? ""}</td>
    </tr>`)
    .join("");

  const total = expenses.reduce((sum, e) => sum + e.amount, 0);
  document.getElementById("total").textContent = `合計: ${total.toLocaleString()}円`;
}

document.getElementById("expense-form").addEventListener("submit", async (event) => {
  event.preventDefault();  // ページ再読み込みを止める
  await createExpense({
    date: document.getElementById("date").value,
    categoryId: Number(document.getElementById("categoryId").value),
    amount: Number(document.getElementById("amount").value),
    memo: document.getElementById("memo").value
  });
  await renderExpenses();  // 登録後に一覧を再描画
});

renderCategories();
renderExpenses();

index.html をブラウザで開けば動きます。フォームから登録すると、一覧と合計が更新され、db.json にもデータが増えます。

ここで一つ白状すると、このコードには「分離の議論の種」が仕込んであります。合計金額の計算(reduce の行)は app.js にあります。表示のための集計なら UI の仕事、業務ルール(例: 予算超過の判定)ならば機能側の仕事。あなたはどちらだと思いますか? AI と議論してみてください。正解を暗記するより、境界線を自分で引けることが大切です。

ここがポイント

  • ファイルをダブルクリックで開いて fetch が失敗する場合は CORS やローカルファイル制約が原因のことがある。JSON Server は既定で CORS を許可しているので通常は動くが、詰まったら npx serve ui などの簡易HTTPサーバ経由で開く
  • app.js に URL を書きたくなったら手が滑っている。URL は api.js だけに置く
  • 発展: この構成のまま React に移行すると、api.js はそのまま使え、app.js がコンポーネントに変わる。分離しておくと乗り換えが安い

コラム

「表示とロジックを分けろ」は最新の教えではありません。1979年、Smalltalk の開発者トリグヴェ・リーンスカウクが提唱した MVC が元祖で、当時から「モデルはビューを知らない」が鉄則でした。40年以上たっても、フレームワークの名前(MVC、MVVM、React の関数コンポーネント)が変わるだけで、原則は同じです。原則を身につければ、流行が変わっても慌てずに済みます。

💬 AIに聞いてみよう

  • 「fetch の結果が Promise { } と表示される。何を間違えている? 修正方法と、そもそも Promise とは何かを初心者向けに教えて」
  • 「この app.js(貼り付ける)に、機能側に置くべきロジックが混ざっていないかレビューして。混ざっていたらどこに移すべきかも提案して」
  • 「開発者ツールの Network タブで API 呼び出しを確認する手順を、私の画面(一覧表示)を例に教えて」

実習・演習

課題

  1. UI の骨組みを作る(20分)
    • ui/index.htmlui/api.jsui/app.js を作成する(上の例を写経してよい)
    • 自分の開発テーマのリソース名・属性名に置き換える
  2. 一覧表示を動かす(15分)
    • ブラウザで開き、モックAPIのデータが一覧表示されることを確認する
    • 開発者ツールの Network タブで GET リクエストと JSON レスポンスを確認する
  3. 登録を動かす(15分)
    • フォームから POST し、一覧が更新されること、db.json が書き換わることを確認する
  4. 分離レビュー(AIペア作業)(残り時間)
    • app.js と api.js を AI に貼り、「分離が守れているか」をレビューしてもらう
    • 指摘が妥当か自分で判断し、直すか直さないかの理由をメモする

成果物

  • ブラウザで一覧表示と登録が動く ui/ 一式(index.html、api.js、app.js)
  • 分離レビューのメモ(AI の指摘と自分の判断1件以上)

ヒント

  • まず写経で動かし、その後に自分のテーマへ置き換える。二段階に分けると切り分けが楽
  • 画面に何も出ないときは、開発者ツールの Console タブを最初に開く。赤いエラーが道しるべ
  • ワイヤーフレーム(Day 2)を横に置き、「この画面のこの欄はどの属性か」を対応させながら作る

まとめ(5分)

今回学んだことを一言でまとめると「UI は fetch で契約(API)を呼ぶだけの存在にする。それがプレゼンテーションと機能の分離の実装形」です。

  • fetch は curl の JavaScript 版。GET/POST がそのまま書ける
  • api.js(通信)と app.js(描画)を分けると、変更の影響範囲が狭くなる
  • エラーはシーケンス図の矢印を上から順に切り分ける

次のセッションでは、この土台を自分の開発テーマに合わせて拡張し、主要ユースケースを通しで動かします。GitHub への push もそこで行います。

🔄 振り返りチェック

  • fetch で POST するときに指定する3つの要素(メソッド・ヘッダ・ボディ)を言えますか?
  • api.js と app.js の役割分担を「知らない」という言葉を使って説明できますか?
  • 画面にデータが出ないとき、最初に確認する場所を2つ挙げられますか?

補足資料

  • 参考リンク: MDN「Fetch API の使用」、MDN「JavaScript の非同期処理」、Chrome DevTools 公式ドキュメント(Network タブ)
  • 発展課題: 削除ボタンを一覧の各行に追加し、DELETE /expenses/:id を呼んで一覧を更新する。さらに余力があれば、同じ api.js を使って React で一覧画面を書き直し、「api.js が無傷である」ことを確認する

学習ガイド

想定される質問と回答例

質問 ヒント
async/await と .then() はどちらを使うべき? どちらでも動く。この研修では読みやすい async/await に統一。AI に両者の書き換え例を出してもらうと理解が深まる
APIのURLをHTMLに書いてはだめ? 動くが、変更時に探し回ることになる。「URL は api.js の API_BASE だけ」と決めると窓口が一本化される
innerHTML での描画は安全? 学習用途では可。外部入力をそのまま埋め込むと XSS の危険がある。発展課題として AI に textContent や escape の方法を聞いてみる

つまずきやすいポイント

つまずきポイント ヒント
await 忘れで Promise { } が表示される・undefined になる fetch と response.json() の前に await があるか確認。関数側にも async が必要。「Promise と出たら await 忘れ」
画面が真っ白でエラーの場所がわからない 開発者ツールの Console→Network の順に見る。API が生きているかは curl かブラウザ直叩きで先に確認し、UI と機能のどちら側の問題か切り分ける
CORS エラーや file:// 直開きで fetch が失敗する JSON Server が起動しているか、URL のポートが合っているかを確認。だめなら npx serve ui で簡易HTTPサーバ経由で開く
描画コードの中に URL や計算ルールを書いてしまい分離が崩れる 「URL は api.js、業務ルールは機能側、表示整形だけ app.js」を合言葉に。書いた後に AI へ分離レビューを依頼する
読み上げを開始します...

AIに質問する