第29章 HTTP導入①:ルーティング→ユースケース呼ぶだけ 🚪🌐
![hex_ts_study_029[(./picture/hex_ts_study_029_http_adapter_express.png)
この章は「HTTPの入口を増やす」回だよ😊 CLIで動いてる中心(ユースケース/ドメイン)はそのままに、HTTPを“差し込み口”として追加します💖
1 この章のゴール 🎯✨
できるようになること👇
/todosみたいなURLにアクセスされたら、ユースケースを呼ぶだけにできる🙌- Controllerを薄く保つコツがわかる🥗✨
- 「中心はHTTPを知らない」を体験できる🛡️🔌
2 今回の方針 合言葉はこれ 🗣️✨
- Controllerは翻訳係🧩 「HTTPの世界 → DTO → ユースケース呼ぶ → 返す」だけ!
- 中心は静かに🧠🌿
中心(usecase/domain)は
expressもRequestも知らない🙅♀️
3 使うHTTPフレームワークは Express v5 にするよ 🚀🧰
理由はシンプルで安心だから😊
- Express v5 は正式リリース済みで、現在も更新が続いてるよ📈 (expressjs.com)
- async/await の例外がそのままエラーハンドラに流れる(Promiseのrejectを自動で拾う)ので、Controllerがさらに薄くできる✨ (expressjs.com)
express.json()も 標準ミドルウェアとして使えるよ🧴 (expressjs.com)- TypeScript の型は
@types/expressが v5 系で提供されてるよ🧷 (npm)
4 まずはインストール 📦✨
(npm例)
npm i express
npm i -D @types/express tsx
tsxは TypeScriptをそのまま実行しやすい定番枠だよ⚡ (GitHub)- Nodeは安定運用なら LTS系が基本で、2026-01時点だと v24 が Active LTS 側だよ🧱 (nodejs.org)
5 ルーティングの形を決めよう 🗺️✨
今回のAPIは超ミニでOK🙆♀️
GET /todos👉 一覧POST /todos👉 追加POST /todos/:id/complete👉 完了
「RESTっぽい形」にしておくと、あとで育てやすいよ🌱💕
6 フォルダ配置はここに置く 📁🧭
この章で増えるのは「HTTPの入口」だけ!
src/
adapters/
inbound/
http/
createHttpApp.ts
todoRoutes.ts
errorMiddleware.ts
server.ts
7 コード 入口の本体を作る 🧩🌐
7.1 ルーター todoRoutes.ts 🚪➡️🧠
ポイント:ルートの中でやるのは3つだけ ① 受け取る → ② ユースケース呼ぶ → ③ 返す
// src/adapters/inbound/http/todoRoutes.ts
import { Router, type Request, type Response } from "express";
type AddTodoUseCase = {
execute(input: { title: string }): Promise<{ id: string; title: string; completed: boolean }>;
};
type ListTodosUseCase = {
execute(): Promise<{ items: Array<{ id: string; title: string; completed: boolean }> }>;
};
type CompleteTodoUseCase = {
execute(input: { id: string }): Promise<{ id: string; title: string; completed: boolean }>;
};
export function createTodoRouter(deps: {
addTodo: AddTodoUseCase;
listTodos: ListTodosUseCase;
completeTodo: CompleteTodoUseCase;
}) {
const router = Router();
router.get("/todos", async (_req: Request, res: Response) => {
const output = await deps.listTodos.execute();
res.json(output);
});
router.post("/todos", async (req: Request, res: Response) => {
// ここは次章で本格バリデーションするよ😊
const title = String(req.body?.title ?? "");
const output = await deps.addTodo.execute({ title });
res.status(201).json(output);
});
router.post("/todos/:id/complete", async (req: Request, res: Response) => {
const id = String(req.params.id ?? "");
const output = await deps.completeTodo.execute({ id });
res.json(output);
});
return router;
}
Express v5 は async handler の例外をエラーハンドラへ流してくれるので、ここで
try/catchを増やさなくても運用しやすいよ✨ (expressjs.com)
7.2 アプリ組み立て createHttpApp.ts 🧩🏗️
// src/adapters/inbound/http/createHttpApp.ts
import express from "express";
import { createTodoRouter } from "./todoRoutes";
import { errorMiddleware } from "./errorMiddleware";
export function createHttpApp(deps: Parameters<typeof createTodoRouter>[0]) {
const app = express();
// JSONボディ受け取り(標準ミドルウェア)🧴
app.use(express.json()); // Express 5.x API にあるよ :contentReference[oaicite:7]{index=7}
// ルーティング(入口は薄く)🚪✨
app.use(createTodoRouter(deps));
// エラーは最後でまとめて変換 🧯
app.use(errorMiddleware);
return app;
}
7.3 エラーミドルウェア errorMiddleware.ts 🧯✨
ここも「薄く」ね🥗 (本格エラー設計は後の章でやるけど、最低限あると安心)
// src/adapters/inbound/http/errorMiddleware.ts
import type { Request, Response, NextFunction } from "express";
export function errorMiddleware(err: unknown, _req: Request, res: Response, _next: NextFunction) {
// とりあえず最小:詳細は後の章で育てる🌱
const message = err instanceof Error ? err.message : "Unknown error";
res.status(500).json({ message });
}
Express v5 のエラーハンドリングの基本は公式ガイドにまとまってるよ🧭 (expressjs.com)
7.4 起動ファイル server.ts 🚀🌐
// src/adapters/inbound/http/server.ts
import { createHttpApp } from "./createHttpApp";
// ここは仮の依存注入:あなたのプロジェクトのusecase達に差し替えてね😊
// 例:compositionRoot から { addTodo, listTodos, completeTodo } を受け取る感じ✨
import { buildTodoUseCases } from "../../compositionRoot/buildTodoUseCases";
const port = Number(process.env.PORT ?? 3000);
const deps = buildTodoUseCases();
const app = createHttpApp(deps);
app.listen(port, () => {
console.log(`HTTP server listening on http://localhost:${port}`);
});
8 起動コマンド 🏃♀️💨
// package.json
{
"scripts": {
"dev:http": "tsx watch src/adapters/inbound/http/server.ts"
}
}
tsx は TypeScriptを実行しやすい定番ツールとして使われてるよ⚡ (GitHub)
9 動作チェック まずは3発だけ撃とう 💥😊
PowerShellだと curl が別物なことがあるので、curl.exe を使うと安定🙆♀️
9.1 追加 POST /todos 📝
curl.exe -X POST http://localhost:3000/todos `
-H "Content-Type: application/json" `
-d "{\"title\":\"牛乳を買う\"}"
9.2 一覧 GET /todos 📋
curl.exe http://localhost:3000/todos
9.3 完了 POST /todos/:id/complete ✅
curl.exe -X POST http://localhost:3000/todos/123/complete
10 Controllerが薄いかチェックする ✅🥗✨
ControllerでやってOK👇
- ✅
reqから必要情報を拾う - ✅ DTOを作る
- ✅ usecaseを呼ぶ
- ✅
res.json()で返す
Controllerでやったら太るやつ👇
- ❌ 業務ルール(「空文字禁止」など)をガッツリ書く
- ❌ 状態遷移(完了二重適用の判断)をする
- ❌ Repositoryを直接触る
「太った瞬間に中心が汚れる」って覚えてね😱🧼
11 AIに頼るならここが安全 🤖💖
11.1 ルーティング雛形を作らせるプロンプト 📝
Express v5 + TypeScript で Router を作ってください。
制約:
- ルート内は「reqから値→DTO→usecase.execute→res.json」のみ
- ドメイン/ユースケース側は express を import しない
- ルートは GET /todos, POST /todos, POST /todos/:id/complete
11.2 自爆防止チェック質問 🔍⚠️
このHTTPアダプタのコードは「usecase/domainがHTTPを知らない」を守れていますか?
express型(Request/Response)が中心へ漏れていないかも確認して。
12 まとめ 🎁💕
- HTTPは Inbound Adapter として追加するだけ🌐🔌
- ルーティングは ユースケース呼ぶだけにして薄く🥗✨
- Express v5 は async/await の扱いが楽で、エラーハンドラへ流せるのが嬉しい🧯 (expressjs.com)
- 次章で Request→DTO変換とレスポンス整形をキレイにするよ🔁📮💖
次の「第30章」も、この流れのまま気持ちよくいけるよ〜!😊🌸