iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
自我挑戰組

《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》系列 第 25

Day25 - 持續成長學習藍圖 - TypeScript(Express + Prisma + TypeScript 整合)

  • 分享至 

  • xImage
  •  

昨天我成功用 Prisma 操作資料庫,感覺就像魔法一樣 ✨
今天要更進一步,讓 Prisma、Express、TypeScript 真正「協作」,
打造出一個 型別安全又乾淨的 Todo API 專案架構


1️⃣ 重新整理專案架構

今天要讓專案更有層次,建立幾個資料夾:

src/
 ├── prisma/
 │   └── client.ts
 ├── dto/
 │   ├── create-todo.dto.ts
 │   └── update-todo.dto.ts
 ├── routes/
 │   └── todo.routes.ts
 ├── middleware/
 │   └── validate-dto.ts
 ├── index.ts
 └── types/
     └── index.d.ts

這樣之後維護起來更直覺,每個區塊只負責一件事。


2️⃣ Prisma Client 抽出成模組

src/prisma/client.ts

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();
export default prisma;

這樣之後要用 Prisma 的地方只要:

import prisma from "../prisma/client";

3️⃣ 建立 DTO(資料驗證)

沿用之前 class-validator 的寫法:

src/dto/create-todo.dto.ts

import { IsString, IsOptional, Length } from "class-validator";

export class CreateTodoDto {
  @IsString()
  @Length(1, 50)
  task: string;

  @IsOptional()
  @IsString()
  note?: string;
}

src/dto/update-todo.dto.ts

import { IsOptional, IsString, IsBoolean } from "class-validator";

export class UpdateTodoDto {
  @IsOptional()
  @IsString()
  task?: string;

  @IsOptional()
  @IsBoolean()
  done?: boolean;

  @IsOptional()
  @IsString()
  note?: string;
}

4️⃣ 建立驗證 Middleware

src/middleware/validate-dto.ts

import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { Request, Response, NextFunction } from "express";

export function validateDto(dtoClass: any) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const instance = plainToInstance(dtoClass, req.body);
    const errors = await validate(instance);
    if (errors.length > 0) {
      const messages = errors.map(err => Object.values(err.constraints || {})).flat();
      return res.status(400).json({ errors: messages });
    }
    next();
  };
}

5️⃣ Todo 路由整合 Prisma

src/routes/todo.routes.ts

import { Router } from "express";
import prisma from "../prisma/client";
import { validateDto } from "../middleware/validate-dto";
import { CreateTodoDto } from "../dto/create-todo.dto";
import { UpdateTodoDto } from "../dto/update-todo.dto";

const router = Router();

// GET 所有 Todo
router.get("/", async (req, res) => {
  const todos = await prisma.todo.findMany();
  res.json(todos);
});

// POST 新增 Todo
router.post("/", validateDto(CreateTodoDto), async (req, res) => {
  const { task, note } = req.body;
  const todo = await prisma.todo.create({ data: { task, note } });
  res.status(201).json(todo);
});

// PUT 更新 Todo
router.put("/:id", validateDto(UpdateTodoDto), async (req, res) => {
  const id = Number(req.params.id);
  const { task, done, note } = req.body;
  try {
    const updated = await prisma.todo.update({
      where: { id },
      data: { task, done, note },
    });
    res.json(updated);
  } catch {
    res.status(404).json({ error: "找不到這筆 Todo" });
  }
});

// DELETE 刪除 Todo
router.delete("/:id", async (req, res) => {
  const id = Number(req.params.id);
  try {
    await prisma.todo.delete({ where: { id } });
    res.json({ message: "Todo 已刪除" });
  } catch {
    res.status(404).json({ error: "找不到這筆 Todo" });
  }
});

export default router;

6️⃣ 主程式整合

src/index.ts

import "reflect-metadata";
import express from "express";
import todoRoutes from "./routes/todo.routes";

const app = express();
app.use(express.json());
app.use("/todos", todoRoutes);

app.listen(3000, () => {
  console.log("🚀 Server running on http://localhost:3000");
});

7️⃣ 實際測試

新增一筆 Todo

curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"task": "Day 25 測試 Prisma 整合", "note": "OK"}'

回傳:

{
  "id": 1,
  "task": "Day 25 測試 Prisma 整合",
  "done": false,
  "note": "OK",
  "createdAt": "2025-10-10T10:00:00.000Z"
}

8️⃣ Prisma 型別的強大提示

例如在編輯 router.get("/") 時,
輸入 prisma.todo.findMany({ where: { ... } })
TypeScript 會即時提示 Todo 的所有欄位(task、done、note 等),
這就是 Prisma + TS 最強大的地方——全自動型別安全 🧩


🎯 學習心得 / 今日收穫

今天感覺就像正式組起了一個「乾淨、可維護的 API 架構」。

✨ 收穫整理:

  • Prisma 幫我解決資料庫操作的繁瑣 SQL
  • TypeScript 讓每個層都有清楚的型別定義
  • class-validator 與 DTO 保證輸入正確
  • 專案結構乾淨、清楚、好擴充

以前寫 Express API 只求能跑,
今天的版本已經有明確架構、驗證與型別安全,
真的更接近「專業後端專案」的樣子。


上一篇
Day24 - 持續成長學習藍圖 - TypeScript(Prisma / TypeORM 基礎)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言