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

開発テーマに沿った実装①(コア機能・API側)

概要

  • 日程: Day 4 / セッション 03
  • 時間: 10:10-11:25
  • 形式: 実習
  • ゴール: 自チームの主要情報モデル2つ以上について、CRUD(Create/Read/Update/Delete)が動作するREST APIを実装し、curlで全エンドポイントが正しく応答する
  • 学習形式: ハンズオン実習(AIペアプログラミング、チーム協働)

導入(5分)

前のセッションで「API仕様(モック)」を作りました。仕様を見たUI担当は安心して画面を作り始められます。しかし、いつまでもモックのままでは「データを保存する」「IDを採番する」「条件で絞り込む」といった本物の機能は動きません。

このセッションでは、モックを本物のAPIに置き換えます。情報の設計(Day 2の情報モデル)とシステムの設計(API実装)を分離した状態で、ロジックを書いていく75分間です。

ここで覚えておいてほしい原則をひとつ。情報モデルの形をそのままAPI仕様に、APIの形をそのまま実装に写像します。情報定義書→情報モデル→API仕様→実装、と一直線につながっているかを常に確認してください。途中で勝手に変えると、Day 1の「ペルソナへの価値提供」が崩れます。

本編(10分)

1. 実装の最小構成:Node.js + Express の例

研修ではNode.jsを推奨します。理由は、UIのJavaScriptと同じ言語でAPI側も書けるため、文脈切替コストが低いからです。

最小のCRUDサーバはこの形です。

// app.js
const express = require('express');
const app = express();
app.use(express.json());

// インメモリのデータストア(本物のDBは後でつなぐ)
let recipes = [
  { id: 1, title: 'カレーライス', time: 30 },
  { id: 2, title: 'オムライス', time: 20 }
];
let nextId = 3;

// 一覧取得
app.get('/recipes', (req, res) => {
  res.json(recipes);
});

// 1件取得
app.get('/recipes/:id', (req, res) => {
  const item = recipes.find(r => r.id === Number(req.params.id));
  if (!item) return res.status(404).json({ error: 'not found' });
  res.json(item);
});

// 新規作成
app.post('/recipes', (req, res) => {
  const item = { id: nextId++, ...req.body };
  recipes.push(item);
  res.status(201).json(item);
});

// 更新
app.put('/recipes/:id', (req, res) => {
  const idx = recipes.findIndex(r => r.id === Number(req.params.id));
  if (idx < 0) return res.status(404).json({ error: 'not found' });
  recipes[idx] = { ...recipes[idx], ...req.body };
  res.json(recipes[idx]);
});

// 削除
app.delete('/recipes/:id', (req, res) => {
  recipes = recipes.filter(r => r.id !== Number(req.params.id));
  res.status(204).end();
});

app.listen(3000, () => console.log('http://localhost:3000'));

起動と動作確認:

npm init -y && npm install express
node app.js

# 別ターミナルで
curl http://localhost:3000/recipes
curl -X POST -H "Content-Type: application/json" \
     -d '{"title":"肉じゃが","time":40}' \
     http://localhost:3000/recipes

ここがポイント

  • データは最初メモリ配列でOK。永続化(ファイル/RDB)は時間があれば後でつなぐ
  • 各エンドポイントは「情報モデルへの1操作」に対応する(Day 2の状態遷移を思い出す)
  • ステータスコードを守る:取得=200、作成=201、削除=204、見つからない=404

コラム:マイクロサービスの始まり方

Netflixは2010年頃、巨大な一枚岩のJavaサーバを「数百個の小さなAPI」へ分解する大改造を行いました。これがマイクロサービスのはしりです。その際、各チームが守ったのが「自分のAPIは情報モデル単位で切る」というルール。皆さんが今日「モデルごとにAPIを分ける」のは、世界的なベストプラクティスのミニチュア体験です。

2. CRUDと状態遷移の対応表

Day 2で書いた状態遷移図を、もう一度開いてください。状態遷移の矢印は、ほぼそのままAPIエンドポイントに対応します。

stateDiagram-v2 [*] --> Draft: POST /recipes (作成) Draft --> Published: PUT /recipes/:id (更新) Published --> Updated: PUT /recipes/:id (更新) Updated --> Deleted: DELETE /recipes/:id (削除) Deleted --> [*]
状態遷移 HTTPメソッド エンドポイント
新規 → ドラフト POST /recipes
ドラフト → 公開 PUT /recipes/:id
公開 → 更新 PUT /recipes/:id
削除 DELETE /recipes/:id
参照 GET /recipes または /recipes/:id

ここがポイント

状態遷移とAPIが対応していれば、仕様の抜け漏れが目に見えます。「この遷移、どのAPIで起こすの?」と問うだけで欠けが見つかります。

コラム:CRUDという略語の起源

1983年、ジェームス・マーティンというコンサルタントが著書で初めて「Create / Read / Update / Delete」をまとめてCRUDと呼びました。当時はデータベース操作の文脈でしたが、今やAPIだけでなくUIの基本パターンとしても定着しています。「全業務はCRUDの組み合わせに分解できる」というのは40年前から言われ続けている真理です。

3. 複数モデルの設計:URLとリレーション

情報モデルが2つ以上ある場合、URL設計でリレーションを表現できます。

flowchart LR U["/users"] --> O["/users/:id/orders"] O --> I["/orders/:id/items"]

例:レシピサービスで「ユーザ」「お気に入りレシピ」の2モデルがある場合

URL 意味
GET /users ユーザ一覧
GET /users/:id ユーザ詳細
GET /users/:id/favorites ユーザのお気に入り一覧
POST /users/:id/favorites お気に入りに追加
DELETE /users/:id/favorites/:rid お気に入りから削除

ここがポイント

URLは名詞、メソッドが動詞/getUserFavoritesのような動詞入りURLは非RESTです。「誰の何を、どうする」をURLとメソッドで分担させます。

💬 AIに聞いてみよう

ここまでの内容で疑問があれば、AIに質問してみましょう。たとえば:

  • 「自分のチームのモデル『〇〇』『△△』のCRUDをExpressで書いて」
  • POSTPUTをリレーションのあるリソースでどう書き分ける?」
  • 「Node.js以外(Python Flask、Ruby Sinatra)の最小CRUDサーバの例も見せて」

実習(55分)

課題

自チームの主要情報モデル2つ以上を選び、それぞれのCRUDを実装してください。最低限の達成基準は:

  • 2モデルそれぞれに対し、GET一覧 / GET詳細 / POST / PUT / DELETE が動作する
  • curlまたはfetchで全エンドポイントが期待通り応答する
  • 適切なステータスコード(200/201/204/404)を返す

成果物

  • 動作するAPIサーバのソースコード(GitHubにプッシュ)
  • READMEに起動方法(npm install && node app.js等)
  • curl実行ログ または Postman/apiaryコレクション

ヒント

  • チーム内で「Aさん:モデル①担当」「Bさん:モデル②担当」と分業すると並行できる
  • まずインメモリ配列でCRUDを完成させてから、余裕があればファイル保存(fs.writeFileSync)へ拡張
  • バリデーション(必須項目チェック等)は最後でよい
  • 詰まったら次のフレーズでAIに頼む:「次のエラーが出た:『...』 原因と修正案を示して」
  • UI担当はこの間にAPI仕様(モック)からfetchコードの下書きを進める

まとめ(5分)

このセッションで皆さんは、Day 2の情報モデル・状態遷移を動くコードへ落とし込みました。情報の設計(モデル)とシステムの設計(API実装)が、別々のレイヤーでつながっている感覚を体感したはずです。

次のセッションでは、UI担当がモックアップからこのAPIを呼び出すコードを書きます。「ここまで設計してきた成果が、画面の上で動く」が現実になります。

🔄 振り返りチェック

  • 自チームのAPIエンドポイント一覧を空で書けますか?
  • 各エンドポイントが返すステータスコードを言えますか?
  • 状態遷移とCRUDの対応が「抜けなく」取れていますか?

補足資料

  • 参考リンク: Express公式ドキュメント、RESTful API設計ガイドライン(Microsoft / Google)
  • 発展課題: バリデーション(Joi / Zod等)と、簡易ログ出力(morganなど)を追加する

学習ガイド

想定される質問と回答例

質問 ヒント
データベースなしで本当にいい? OK。Day 4のゴールは「分離して動く」こと。永続化は発展課題
CORSでエラーが出る app.use(require('cors')())を最初に入れる、と覚える
:idが文字列で来てつまずく Number(req.params.id)で数値化する

つまずきやすいポイント

つまずきポイント ヒント
URLに動詞を入れてしまう URLは名詞、操作はHTTPメソッドが担う
404を返さず空配列を返す 「存在しない」と「空」は別。404を返すべきケースを意識
「全部1ファイル」で複雑化 モデル単位でファイル分割すると見通しが良くなる
読み上げを開始します...

AIに質問する