在學習 Express + TypeScript + TypeORM 的過程中,TodoList API
是非常適合新手上手的練習案例。
因為它的邏輯簡單(新增、讀取、更新、刪除),卻又涵蓋了 RESTful API 的核心概念:
- CRUD(Create / Read / Update / Delete)
- Controller / Route 分層
- 與資料庫的互動(Repository)
這樣的練習不僅可以打好基礎,還能快速理解 後端架構設計 的常見模式。
建立 Controller
Controller(控制器)是 專門負責處理業務邏輯 的地方:
- 收到請求 (
req
) → 驗證/處理 → 回傳回應 (res
)
- 不直接決定路由,而是提供「功能」讓 Route 使用
- 好處是:程式碼結構清楚、可讀性佳、方便測試與維護
建立 src/controllers/todoController.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import { Request, Response, NextFunction } from "express"; import { AppDataSource } from "../config/db"; import { Todo } from "../entities/Todo";
const todoRepository = AppDataSource.getRepository(Todo);
export async function getTodos( req: Request, res: Response, next: NextFunction ): Promise<void> { try { const todos = await todoRepository.find(); res.json({ status: "success", data: todos }); } catch (error) { next(error); } }
export async function createTodo( req: Request, res: Response, next: NextFunction ): Promise<void> { try { const { title } = req.body; if (!title) { res.status(400).json({ status: "error", message: "Title is required" }); return; } const newTodo = todoRepository.create({ title }); const savedTodo = await todoRepository.save(newTodo); res.status(201).json({ status: "success", data: savedTodo }); } catch (error) { next(error); } }
export async function updateTodo( req: Request, res: Response, next: NextFunction ): Promise<void> { try { const { id } = req.params; const { title, completed } = req.body; const todo = await todoRepository.findOneBy({ id });
if (!todo) { res.status(404).json({ status: "error", message: "Todo not found" }); return; }
todo.title = title !== undefined ? title : todo.title; todo.completed = completed !== undefined ? completed : todo.completed; const updatedTodo = await todoRepository.save(todo); res.json({ status: "success", data: updatedTodo }); } catch (error) { next(error); } }
export async function deleteTodo( req: Request, res: Response, next: NextFunction ): Promise<void> { try { const { id } = req.params; const result = await todoRepository.delete({ id }); if (result.affected === 0) { res.status(404).json({ status: "error", message: "Todo not found" }); return; } res.json({ status: "success", data: result }); } catch (error) { next(error); } }
|
設定路由
Route(路由)的工作就是 決定請求要交給哪個 Controller 處理。
它就像是 導航地圖:
GET /todos
→ 查詢所有代辦事項 → getTodos
POST /todos
→ 建立新代辦 → createTodo
PUT /todos/:id
→ 更新某筆代辦 → updateTodo
DELETE /todos/:id
→ 刪除某筆代辦 → deleteTodo
這樣一來,Controller 和 Route 的責任就分得很清楚:
- Route = 請求分配器
- Controller = 處理邏輯
建立 src/routes/todoRoutes.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { Router } from "express"; import { getTodos, createTodo, updateTodo, deleteTodo, } from "../controllers/todoController";
const router = Router();
router.get("/", getTodos); router.post("/", createTodo); router.put("/:id", updateTodo); router.delete("/:id", deleteTodo);
export default router;
|
整合到主程式
最後一步就是在 app.ts
裡,把 todoRoutes
整合進主程式。
這樣當使用者發送請求到 /api/todos
時,Express 就會把它交給我們剛剛寫好的 todoRoutes
,再由對應的 Controller 去處理。
修改 src/app.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import "reflect-metadata"; import express from "express"; import { AppDataSource } from "./config/db"; import todoRoutes from "./routes/todoRoutes"; import dotenv from "dotenv";
dotenv.config();
const app = express(); app.use(express.json());
app.use("/api/todos", todoRoutes);
app.get("/", (req, res) => { res.send("Hello, iThome 2025!"); });
const PORT = process.env.PORT || 3000;
AppDataSource.initialize() .then(() => { console.log("📦 DB Connected!"); app.listen(PORT, () => { console.log(`🚀 Server running on http://localhost:${PORT}`); }); }) .catch((err) => { console.error("❌ DB connection failed:", err); });
|
👉 到這裡,我們就完成了一個最基礎的 TodoList API。
但是目前還沒有資料庫可以測試 (還不能用 Postman 打 API 😂),下一篇我們就來介紹 Render 服務上的資料庫應用,把整段串接起來。
補充資料
Github 範例程式碼
commit : Day 8 initialize todo‑list API