閱讀進度 0%
0%

結合 Zod 與 TypeScript,為 AI 應用建立可靠的資料驗證機制

TypeScript開發工具前端開發資料驗證AI約 9 分鐘閱讀
結合 TypeScript 與 Zod,為 AI 專案打造類型安全與執行時驗證兼具的開發體驗。本文示範如何在 Vibe Coding 工作流中強化資料可信度與錯誤處理。

⚠️ 時效警告:本文發佈於 2025 年,文中提到的 Zod 3 API 已被 Zod 4 取代(2025 年 5 月正式發佈)。內容保留作為歷史紀錄,內文程式碼範例已更新為 Zod 4 語法。

在專案開發初期,我原本只是單純想透過 AI 工具提升程式撰寫效率,沒想到很快就遇到資料格式與型別混亂的問題。模型生成的結果,外觀看似正確,卻時常潛藏型別錯誤或結構異常,輕則導致前端 bug,重則讓整個應用崩潰。這讓我深刻體會到,僅靠 TypeScript 的靜態型別檢查遠遠不夠,真正可靠的資料驗證必須貫徹到執行時階段。

Zod 正好補足了這一環。它專為 TypeScript 設計,讓你用宣告式方式一次定義驗證規則和型別推斷,省去繁瑣的重複定義。從 AI 輸出、API 資料到表單輸入,Zod 都能即時攔截錯誤資料,讓資料流更安全、開發更安心。

這篇文章是我將 Zod 實際應用於 AI 工具開發、API 串接與前端資料處理的經驗總結。不論你是要提升資料可信度,還是追求開發效率與型別安全,這裡都能幫你避開坑洞、打造更穩健的 TypeScript 專案。

Zod 是什麼?為什麼適合 AI 應用?

TypeScript

Zod 的設計理念是消除重複的類型宣告。在傳統的 TypeScript 開發中,我們經常需要先定義一個類型,然後再為該資料編寫驗證邏輯,這導致了重複的程式碼和潛在的不一致性。Zod 通過讓你定義一次驗證器,然後自動推斷出相應的 TypeScript 類型,解決了這個問題。

與其他驗證庫(如 Yup)相比,Zod 在多個層面展現出明顯優勢。首先,Zod 自設計之初即高度整合 TypeScript,能實現更佳的型別推斷,減少潛在錯誤,這是 Yup 等庫後期才逐步補強的特性。此外,Zod 的包體積僅約 8KB,遠小於 Yup 的 24KB,對於前端專案尤為適合,能有效優化載入體驗。

其次,Zod 採用不可變 API,所有方法都返回新實例,不會修改原始對象,這更符合現代 JavaScript 的函數式編程理念。這些設計讓 Zod 不僅在 TypeScript 專案中佔有優勢,也成為追求簡潔、高效、型別安全開發者的首選。

如何安裝 Zod

安裝 Zod 很簡單,只需要執行以下命令即可:

npm install zod

或者使用 yarn:

yarn add zod

這樣你就可以開始使用 Zod 了。

Zod 基本用法與資料驗證範例

Zod 是一個為 TypeScript 設計的宣告式模式驗證工具。它能同時進行「執行時資料驗證」與「型別推斷」,讓你只需定義一次資料結構,就能自動獲得型別安全與驗證能力。這對於處理外部 API 資料、AI 輸出或表單輸入等不可信來源,特別重要。以下範例展示 Zod 的基本用法:

import { z } from "zod";

// 建立字串模式
const stringSchema = z.string();

// 驗證成功
stringSchema.parse("hello"); // => "hello"

// 驗證失敗(將拋出 ZodError)
try {
  stringSchema.parse(12);
} catch (error) {
  console.log((error as z.ZodError).issues);
  // [
  //   {
  //     code: 'invalid_type',
  //     expected: 'string',
  //     path: [],
  //     message: 'Invalid input: expected string, received number'
  //   }
  // ]
}

// 安全驗證(不拋出錯誤)
const result = stringSchema.safeParse("hello");
if (result.success) {
  console.log(result.data); // "hello"
} else {
  console.log((result.error as z.ZodError).issues); // ZodError details
}

// 定義物件模式
const UserSchema = z.object({
  username: z.string(),
  email: z.email(),
  age: z.number().min(18),
});

// 型別自動推斷
type User = z.infer<typeof UserSchema>;
// 等同於:{ username: string; email: string; age: number }

在實務開發中,僅僅依靠 TypeScript 靜態型別檢查,往往無法保障資料於執行時的完整性與正確性。Zod 則彌補了這一缺口,讓型別定義與驗證規則合而為一,減少重複維護負擔,同時強化每一道資料流的安全防線。這種 schema 驅動的驗證模式,不僅讓你更安心接收外部 API 或 AI 輸出,也大幅優化前後端協作效率,為現代 AI 應用建立穩健的資料基石。

一次定義,推斷類型:告別重複維護

Zod 最大的優勢之一是消除了在 TypeScript 中常見的類型重複問題。通過 z.infer 可以直接從 Zod 模式推斷出 TypeScript 類型,讓你的類型定義和驗證邏輯保持同步。

import { z } from "zod";

const UserSchema = z.object({
    id: z.uuid(),
    name: z.string().min(2),
    email: z.email(),
    role: z.enum(["admin", "user", "guest"]),
    metadata: z.record(z.string(), z.string()),
    lastLogin: z.date().optional(),
  });
  
  // 自動推斷類型
type User = z.infer<typeof UserSchema>;

const user: User = {
    id: "8dd7d69c-ab17-468c-80b5-c71658aa6b07",
    name: "John Doe",
    email: "john.doe@example.com",    
    role: "admin",
    metadata: {
        age: "30"
    },
    lastLogin: new Date("2023-03-15T12:34:56.789Z")
}

const result = UserSchema.parse(user);
console.log(result);

/*
{
  id: '8dd7d69c-ab17-468c-80b5-c71658aa6b07',
  name: 'John Doe',
  email: 'john.doe@example.com',
  role: 'admin',
  metadata: { age: '30' },
  lastLogin: 2023-03-15T12:34:56.789Z
}
*/

這不僅減少了維護負擔,也讓類型錯誤更早在開發階段被發現,是大幅提升穩定性的第一步。

編譯與執行雙保險,強化型別安全

TypeScript 僅提供編譯時的類型檢查,一旦編譯完成,類型安全就消失了。Zod 彌補了這一缺口,通過在執行時驗證資料結構,確保輸入確實符合預期的類型和格式。

// 表單提交處理器:結合 TypeScript 類型與 Zod 執行時驗證
function handleSubmit(formData: unknown) {
  try {
    // 驗證資料結構與型別
    const validatedData = UserSchema.parse(formData);
    // 通過驗證後的資料具備完整型別安全
    saveToDatabase(validatedData);
    console.log("資料已成功儲存");
  } catch (error) {
    if (error instanceof z.ZodError) {
      // 輸出詳細驗證錯誤訊息
      console.error("表單驗證失敗:", error.issues);
    } else {
      console.error("未知錯誤:", error);
    }
  }
}

結合靜態與動態驗證,讓應用層層把關,提升穩定性與安全性。

沒有依賴,體積小巧,適合前端部署

Long exposure photography of road and cars

Zod 完全沒有依賴其他套件,這使它成為一個極為輕量的解決方案。經過壓縮後,Zod 的體積僅約 8KB,遠小於如 Yup(約 24KB)或 Joi 等同類型驗證庫。這種輕量特性對於需要優化資源載入速度的前端應用尤其重要,能有效縮短頁面初次渲染時間,提升用戶體驗。

此外,Zod 的零依賴設計意味著它不會因外部套件變動而引入額外風險,維護起來更加穩定可靠。這也讓它非常適合在 Serverless、Edge Function、瀏覽器端等對體積和穩定性有嚴格要求的場景下使用。即使在大型單頁應用(SPA)或移動端 PWA 專案中,Zod 也能輕鬆融入,而不會拖慢整體效能。

與此同時,Zod 仍然保有強大且完整的驗證能力,無論是複雜的巢狀物件、陣列、聯合型別,還是用 refine 與 superRefine 實現自定義規則都能輕鬆處理。這讓開發者不必在效能與功能間妥協,能放心地將 Zod 用於要求嚴格的前端與 AI 應用。

豊富的驗證功能

Zod 提供了豐富的內建驗證規則和方法,可以處理從簡單原始類型到複雜巢狀對象的各種資料結構。

import { z } from "zod";

// 字串驗證
const username = z.string()
  .min(3, "用戶名至少需要 3 個字符")
  .max(20, "用戶名不能超過 20 個字符");

// 數字驗證
const price = z.number()
  .positive("價格必須為正數")
  .min(0.01, "價格最少為 0.01");

// 複雜對象驗證
const Product = z.object({
  id: z.uuid(),
  name: z.string().min(3),
  price: z.number().positive(),
  tags: z.array(z.string()),
  stock: z.number().int().nonnegative(),
});

const user = username.parse("calpa");
console.log(user);

const priceValue = price.parse(10.99);
console.log(priceValue);

const product = Product.parse({
  id: "8dd7d69c-ab17-468c-80b5-c71658aa6b07",
  name: "Product A",
  price: 10.99,
  tags: ["tag1", "tag2"],
  stock: 10,
});

console.log(product);

你可以看到輸出結果,calpa、10.99 和 product 都已經通過了驗證。

calpa
10.99
{
  id: '8dd7d69c-ab17-468c-80b5-c71658aa6b07',
  name: 'Product A',
  price: 10.99,
  tags: [ 'tag1', 'tag2' ],
  stock: 10
}

用 refine 與 superRefine 實現自定義規則

通過 .refine() 和 .superRefine() 方法,Zod 允許你添加自定義的驗證邏輯,這在處理複雜業務規則時特別有用。

const passwordForm = z
  .object({
    password: z.string().min(8),
    confirmPassword: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "密碼不匹配",
    path: ["confirmPassword"], // 指定錯誤路徑
  });

驗證函數參數與回傳值,提升邏輯安全性

Zod 的一大亮點是可以直接驗證函數的參數與回傳值型別,確保每一次調用都符合預期結構,讓商業邏輯更嚴謹,減少潛在錯誤。

基本用法

import { z } from "zod";

// 定義一個函數,參數型別為 string 和 number,回傳 boolean
const isLongerThan = z
  .function({
    input: [z.string(), z.number()],
    output: z.boolean(),
  })
  .implement((text, minLength) => text.length > minLength);

// 正確用法
console.log(isLongerThan("hello", 3)); // true

// 錯誤用法將在型別檢查階段被攔截
// isLongerThan(123, "hi"); // ❌ TypeScript 編譯不通過

Zod 讓你像寫型別一樣寫驗證,將型別安全提升到運行階段,讓業務邏輯更可靠!

進階:複雜參數與回傳型別

你可以驗證更複雜的參數結構,例如物件、陣列,甚至多個參數:

const sumNumbers = z
  .function({
    input: [z.array(z.number())],
    output: z.number(),
  })
  .implement((nums) => nums.reduce((a, b) => a + b, 0));

// 正確用法
console.log(sumNumbers([1, 2, 3])); // 6

// 錯誤用法(型別不符)
// sumNumbers("123"); // ❌

用於 API 路由與業務邏輯

這種函數型驗證方式非常適合應用於 API 路由、服務層,或任何要求「輸入/輸出型別安全」的場景。例如:

const registerUser = z
  .function({
    input: [
      z.object({
        username: z.string().min(3),
        password: z.string().min(8),
      }),
    ],
    output: z.object({ id: z.uuid() }),
  })
  .implementAsync(async ({ username, password }) => {
    // 處理註冊邏輯...
    return { id: crypto.randomUUID() };
  });

// 呼叫時自動檢查型別
registerUser({ username: "calpa", password: "supersecret123" }).then(console.log);
// registerUser({ username: "ab", password: "123" }); // ❌ 參數驗證失敗

與 React、tRPC 等框架無縫整合

React.js

Zod 可完美整合於現代前後端框架,例如 React 與 tRPC。它讓你不僅能在前端開發中驗證表單資料、API 請求和回應,還能將型別安全從編譯時延伸到執行時。這種端到端的驗證,大幅提升資料正確性與應用穩定性,有效防止錯誤數據流入,顯著降低潛在風險。

React 結合 Zod 驗證表單資料範例

你可以使用 Zod 來驗證 React 表單資料,以下是一個簡單的範例:

import React, { useState } from "react";
import { z } from "zod";

const schema = z.object({
  email: z.email({ message: "請輸入有效的電子郵件" }),
  age: z
    .number({ error: "年齡必須為數字" })
    .min(18, { message: "必須年滿 18 歲" }),
});

export default function SignupForm() {
  const [form, setForm] = useState({ email: "", age: "" });
  const [error, setError] = useState<string | null>(null);
  const [success, setSuccess] = useState(false);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setForm({ ...form, [e.target.name]: e.target.value });
    setError(null);
    setSuccess(false);
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const parseResult = schema.safeParse({
      email: form.email,
      age: Number(form.age),
    });
    if (!parseResult.success) {
      setError(parseResult.error.issues.map(e => e.message).join(","));
      setSuccess(false);
      return;
    }
    setError(null);
    setSuccess(true);
    // 實際應用場景可在此處發送 API 請求
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="email"
        value={form.email}
        onChange={handleChange}
        placeholder="Email"
        type="email"
        required
      />
      <input
        name="age"
        value={form.age}
        onChange={handleChange}
        placeholder="年齡"
        type="number"
        required
      />
      <button type="submit">提交</button>
      {error && <div style={{ color: "red" }}>{error}</div>}
      {success && <div style={{ color: "green" }}>提交成功!</div>}
    </form>
  );
}

Zod 不僅能夠提升型別安全,更讓你在 React、tRPC 等現代框架中,實現「編譯時 + 執行時」雙重保護。這種即時驗證的能力,大幅降低了資料錯誤進入業務邏輯層的風險。

不論是 API 輸入、前端表單還是複雜嵌套物件,Zod 都能以極簡的語法完成嚴謹的驗證,讓你的 TypeScript 專案更安全、更可靠。

tRPC 結合 Zod 驗證 API 輸入輸出範例

import { z } from "zod";
import { createRouter } from "@trpc/server";

const schema = z.object({
  email: z.email(),
  age: z.number().min(18),
});

const router = createRouter()
  .mutation("signup", {
    input: schema,
    resolve: ({ input }) => {
      // 處理通過驗證的資料
      alert("提交成功!");
    },
  });

對於 tRPC 這類強調全端型別安全的方案,Zod 扮演著「溝通橋樑」的角色。你可以在伺服器端定義好 Zod schema,然後直接推導出 TypeScript 類型,讓前端呼叫 API 時自動擁有正確型別提示,徹底消除型別不一致的隱患。

這種整合方式不僅減少了重複維護型別和驗證邏輯的麻煩,也讓開發流程更自動化、更可靠。無論是處理用戶輸入、API 輸出還是 AI 模型回傳的原始資料,Zod 都能幫你快速攔截錯誤並給出明確回饋,讓你的應用程式在資料層面更加健壯。

在 AI 專案中導入 Zod 的關鍵場景與實例

Saradasish Pradhan

隨著 AI 應用程式的普及,Zod 已成為處理 AI 輸出的重要工具。從 OpenAI、Vercel 到 Google,越來越多的 AI 平台文檔中推薦 Zod 來確保 AI 生成資料符合應用預期結構。這種驗證不僅提升穩定性,也大幅減少資料錯誤導致的 bug 與安全隱患。

舉例來說,假設你要接收一個 LLM 回傳的內容,並對其中的實體進行後續運算:

import { z } from "zod";

// 定義 AI 回傳資料的 schema
const AIResponseSchema = z.object({
  text: z.string(),
  confidence: z.number().min(0).max(1),
  entities: z.array(
    z.object({
      type: z.enum(["person", "organization", "location"]),
      text: z.string(),
      score: z.number().min(0).max(1),
    })
  ),
});

// 處理 AI 回應
async function handleAIResponse(rawResponse: unknown) {
  const result = AIResponseSchema.safeParse(rawResponse);
  if (!result.success) {
    console.error("AI 回應格式無效", result.error.issues);
    return;
  }
  // result.data 已自動型別推導且安全
  processEntities(result.data.entities);
}

// 假設的實體處理函數
function processEntities(entities: Array<{ type: string; text: string; score: number }>) {
  entities.forEach((entity) => {
    console.log(`[${entity.type}] ${entity.text} (score: ${entity.score})`);
  });
}

這種寫法讓你不必擔心 AI 回傳結構突變或資料異常,所有型別檢查與數值範圍都在執行時由 Zod 把關。AI 開發中,這不但減少 debug 時間,更能大幅提升生產環境的穩定性。

結論

Person wearing backpack standing on cliff in front of mountains during daytime

Zod 就像是 TypeScript 的資料守門員,讓你不再擔心資料結構與實際輸入脫節的問題。它橋接了開發者最常面對的兩端:靜態型別與動態資料,無論你面對的是表單、API、還是 AI 模型,都能幫你築起一道清晰又安全的資料牆。

對我來說,這不只是提升開發效率,更是讓 Vibe Coding 真正穩定運作的基礎。如果你也在追求高可靠性的 AI 專案工作流,Zod 絕對值得成為你的資料驗證首選。

TypeScript開發工具前端開發資料驗證AI
關於 Calpa

Calpa 擅長使用 TypeScript、React.js 和 Vue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。

熱門文章