iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Modern Web

我與型別的 30 天約定:TypeScript 入坑實錄系列 第 17

Day 17|型別安全的路由參數與查詢參數驗證:express + zod

  • 分享至 

  • xImage
  •  

昨天我們用 zod 做了 環境變數驗證

今天要把這個概念延伸到 API 輸入驗證

確保「前端送進來的資料」在到達你的程式邏輯前,

就已經是乾淨、合法、型別安全的。


1. 為什麼要驗證 API 輸入?

  • 防呆:前端可能少傳參數、傳錯型別。
  • 防駭:避免惡意請求塞奇怪的值進來。
  • 維護性:TS 可以直接推論型別,不用到處 typeof

2. 安裝套件

我們在 Day 15 已經安裝 express

這裡只要加 zod(如果 Day 16 已裝過就不用再裝):

npm install zod

3. 建立驗證 Middleware

src/middleware/validate.ts

import { AnyZodObject } from "zod";
import { Request, Response, NextFunction } from "express";

export const validate =
  (schema: AnyZodObject) =>
  (req: Request, res: Response, next: NextFunction) => {
    try {
      schema.parse({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      next();
    } catch (err: any) {
      return res.status(400).json({
        message: "Validation error",
        errors: err.errors,
      });
    }
  };

這個 middleware 可以驗證 body、query、params

通過就 next(),失敗就直接回傳錯誤。


4. 定義路由驗證 Schema

src/routes/user.ts

import express from "express";
import { z } from "zod";
import { validate } from "../middleware/validate";

const router = express.Router();

// 驗證 schema
const getUserSchema = z.object({
  params: z.object({
    id: z.string().uuid(), // 必須是 UUID 格式
  }),
  query: z.object({
    includePosts: z
      .string()
      .optional()
      .transform(val => val === "true"), // "true" → boolean
  }),
  body: z.object({}), // GET 沒有 body
});

router.get(
  "/:id",
  validate(getUserSchema),
  (req, res) => {
    // 這裡的 req.params.id 已經保證是 UUID
    res.json({
      id: req.params.id,
      includePosts: req.query.includePosts,
    });
  }
);

export default router;

5. 在主程式掛載路由

src/index.ts

import express from "express";
import userRoutes from "./routes/user";

const app = express();
app.use(express.json());

app.use("/users", userRoutes);

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

6. 測試結果

✅ 正確請求

GET http://localhost:3000/users/550e8400-e29b-41d4-a716-446655440000?includePosts=true

回傳:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "includePosts": true}

❌ 錯誤請求(id 不是 UUID)

GET http://localhost:3000/users/123?includePosts=true

回傳:

{
  "message": "Validation error",
  "errors": [
    {
      "code": "invalid_string",
      "message": "Invalid uuid",
      "path": ["params","id"]
    }
  ]
}

7. 好處

  • 參數驗證集中化 → Controller 更乾淨
  • TS 型別直接從 zod schema 推論,免重複定義
  • 減少 runtime 驗證遺漏的風險

上一篇
Day 16|型別安全的環境變數管理:dotenv + zod
下一篇
Day 18|Prisma + TypeScript:資料庫型別安全實戰
系列文
我與型別的 30 天約定:TypeScript 入坑實錄19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言