開発テーマに沿った実装①(コア機能・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エンドポイントに対応します。
| 状態遷移 | 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設計でリレーションを表現できます。
例:レシピサービスで「ユーザ」「お気に入りレシピ」の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で書いて」
- 「
POSTとPUTをリレーションのあるリソースでどう書き分ける?」 - 「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ファイル」で複雑化 | モデル単位でファイル分割すると見通しが良くなる |