在學習 Express + TypeScript + TypeORM 的過程中,TodoList API
是非常適合新手上手的練習案例。
因為它的邏輯簡單(新增、讀取、更新、刪除),卻又涵蓋了 RESTful API 的核心概念:
這樣的練習不僅可以打好基礎,還能快速理解 後端架構設計 的常見模式。
Controller(控制器)是 專門負責處理業務邏輯 的地方:
req
) → 驗證/處理 → 回傳回應 (res
)建立 src/controllers/todoController.ts
:
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 的責任就分得很清楚:
建立 src/routes/todoRoutes.ts
:
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
:
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); // 加上 Todo 路由
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 服務上的資料庫應用,把整段串接起來。
commit : Day 8 initialize todo‑list API