第3章 まずは“分けない版”を作る①(最小の動く形)😅🔧
今日のゴール🎯✨
- 注文を 作る(create) と 一覧を見る(list) を、1つの
OrderServiceに雑に詰め込んで動かす💨 - 「動くけど、これ…増えたらヤバそう😇」をうっすら感じる👀🌱
ちなみに2026/1/24時点だと、TypeScript の npm “latest” は 5.9.3、Node.js は v24 が Active LTS、VS Code は 1.108 系が安定版として案内されてるよ🧡
(このへんは手順中の @latest で自然に追従する想定!) (NPM)
できあがりイメージ🍙📱
POST /orders→ 注文を作る🧾✨GET /orders→ 注文一覧を返す📋✨- データは メモリ上(配列) に保存(いったん軽く!🪶)
1) プロジェクト作成📁✨(最小でOK)
ターミナルでこれ👇(フォルダ名は好きにどうぞ!)
mkdir cqrs-ch3
cd cqrs-ch3
npm init -y
必要なものを入れる👇(Expressで最小APIにするよ〜🌐)
npm i express
npm i -D typescript tsx @types/express
tsx は「TypeScriptをそのまま実行」しやすいやつ⚡(初心者にやさしい) (GitHub)
2) tsconfig.json を作る🛠️✨
npx tsc --init
できた tsconfig.json を、いったんこの感じに寄せる(全部同じでOK!)👇
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
3) 実行スクリプトを用意🏃♀️💨
package.json の "scripts" をこうする👇
{
"scripts": {
"dev": "tsx watch src/index.ts"
}
}
4) ぜんぶ詰め込み src/index.ts を作る😅📦

フォルダ作って👇
mkdir src
src/index.ts を作って、これをコピペでOK👇(わざと全部ここに入れるよ!)
import express from "express";
import { randomUUID } from "crypto";
/**
* 「分けない版」なので、型もサービスも保存もルーティングもぜんぶ同居😅
* ※この雑さが、あとで効いてくる…!
*/
type OrderStatus = "ORDERED" | "PAID";
type OrderItem = {
menuId: string;
name: string;
price: number; // 本当は通貨や丸めがあるけど、いまは雑でOK🪶
qty: number;
};
type Order = {
id: string;
customerId: string;
items: OrderItem[];
total: number;
status: OrderStatus;
createdAt: string;
};
type CreateOrderRequest = {
customerId: string;
items: OrderItem[];
};
class OrderService {
// DBの代わりに配列で保存(雑だけど最小で動く!)
private orders: Order[] = [];
createOrder(req: CreateOrderRequest): Order {
// いったん雑バリデーション(この章は“最低限”でOK🙂)
if (!req.customerId) {
throw new Error("customerId is required");
}
if (!Array.isArray(req.items) || req.items.length === 0) {
throw new Error("items must be a non-empty array");
}
for (const item of req.items) {
if (!item.menuId) throw new Error("menuId is required");
if (!item.name) throw new Error("name is required");
if (typeof item.price !== "number" || item.price < 0) throw new Error("price must be >= 0");
if (typeof item.qty !== "number" || item.qty <= 0) throw new Error("qty must be > 0");
}
const total = req.items.reduce((sum, i) => sum + i.price * i.qty, 0);
const order: Order = {
id: randomUUID(),
customerId: req.customerId,
items: req.items,
total,
status: "ORDERED",
createdAt: new Date().toISOString()
};
this.orders.push(order);
return order;
}
listOrders(): Order[] {
// そのまま返す(整形とかページングとかは、まだやらない🙂)
return this.orders;
}
}
// --- ここから “API” も同じファイルに同居 😅 ---
const app = express();
app.use(express.json());
const orderService = new OrderService();
app.post("/orders", (req, res) => {
try {
const created = orderService.createOrder(req.body);
res.status(201).json(created);
} catch (e) {
const message = e instanceof Error ? e.message : "unknown error";
res.status(400).json({ error: message });
}
});
app.get("/orders", (_req, res) => {
const list = orderService.listOrders();
res.json(list);
});
const port = 3000;
app.listen(port, () => {
console.log(`✅ server running: http://localhost:${port}`);
});
5) 起動する▶️✨
npm run dev
コンソールにこれが出たらOK👇
- ✅
server running: http://localhost:3000
6) 動作確認する🧪✨(PowerShellでもOK)
注文を作る(POST)🧾🍙
いちばん楽:PowerShell の Invoke-RestMethod 💙
$body = @{
customerId = "u-001"
items = @(
@{ menuId="m-001"; name="カツ丼"; price=650; qty=1 },
@{ menuId="m-002"; name="味噌汁"; price=120; qty=1 }
)
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method Post -Uri "http://localhost:3000/orders" -ContentType "application/json" -Body $body
成功すると id と total とかが返ってくるよ〜✨
一覧を見る(GET)📋✨
Invoke-RestMethod -Method Get -Uri "http://localhost:3000/orders"
7) この章の「わざと雑」ポイント観察👀🌱
動いた!えらい!🎉 …でも、今の OrderService って、もうすでに👇が混ざってる😅
- 入力チェック(バリデーション)🧹
- 合計計算(業務っぽいロジック)🧠
- ID発行(技術都合)🪪
- 保存(DBっぽい責務)🗄️
- 一覧返す(表示寄りの要求が来がち)📋
この状態で、次章で「支払い」「状態遷移」「検索条件」みたいなものが増えると… “1クラスが巨大化して、変更が怖くなる” って流れになりやすいんだよね😇📌
(この“嫌な予感”を、次章でわざと現実にするよ!😆🔥)
8) ミニ演習(5〜15分)🧩✨
演習A:注文に note(備考)を追加📝
CreateOrderRequestにnote?: stringを追加Orderにもnote?: stringを追加createOrderがそれを保存して返すようにする
👉 ポイント:地味だけど、こういう小変更が積み重なると OrderService がどんどん太る🍔😅
演習B:GET /orders?status=ORDERED を追加🔎
app.get("/orders"...でクエリを見るlistOrders()にフィルタ引数を足してもいいし、ルート側でやってもいい
👉 ポイント:「Query(参照)」の都合が入り始めると、更新ロジックと同居してカオス化しやすい🌪️
9) AI活用🤖✨(この章での使いどころ)
💡おすすめの頼み方(コピペOK)👇
- 「今の
CreateOrderRequestに note を追加して、APIの入出力も壊さずに通して」 - 「
GET /ordersに status フィルタを追加して。型がanyにならないように」 - 「今の
OrderServiceの責務が混ざってるところを箇条書きで指摘して」
✅ AIの出力をチェックするコツ
anyが増えてない?(増えてたら負け😇)- エラーが握りつぶされてない?
- 仕様(statusフィルタなど)が勝手に変わってない?
まとめ🎀✨
この章は「分けない版」をあえて作って、
- 最小で動く体験🎉
- そして“混ざり始める匂い”を嗅ぐ👃🌱 ここまでが目的だよ〜😄🧡
次の第4章で、ここに機能を足して “うわ、つらい😵💫” を体験しに行こうね🔥