在專案開發初期,我原本只是單純想透過 AI 工具提升程式撰寫效率,沒想到很快就遇到資料格式與型別混亂的問題。模型生成的結果,外觀看似正確,卻時常潛藏型別錯誤或結構異常,輕則導致前端 bug,重則讓整個應用崩潰。這讓我深刻體會到,僅靠 TypeScript 的靜態型別檢查遠遠不夠,真正可靠的資料驗證必須貫徹到執行時階段。
Zod 正好補足了這一環。它專為 TypeScript 設計,讓你用宣告式方式一次定義驗證規則和型別推斷,省去繁瑣的重複定義。從 AI 輸出、API 資料到表單輸入,Zod 都能即時攔截錯誤資料,讓資料流更安全、開發更安心。
這篇文章是我將 Zod 實際應用於 AI 工具開發、API 串接與前端資料處理的經驗總結。不論你是要提升資料可信度,還是追求開發效率與型別安全,這裡都能幫你避開坑洞、打造更穩健的 TypeScript 專案。
Zod 是什麼?為什麼適合 AI 應用?
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).errors);
// [
// {
// code: 'invalid_type',
// expected: 'string',
// received: 'number',
// path: [],
// message: '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).errors); // ZodError details
}
// 定義物件模式
const UserSchema = z.object({
username: z.string(),
email: z.string().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.string().uuid(),
name: z.string().min(2),
email: z.string().email(),
role: z.enum(["admin", "user", "guest"]),
metadata: z.record(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.errors);
} else {
console.error("未知錯誤:", error);
}
}
}
結合靜態與動態驗證,讓應用層層把關,提升穩定性與安全性。
沒有依賴,體積小巧,適合前端部署
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.string().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()
.args(z.string(), z.number())
.returns(z.boolean())
.implement((text, minLength) => text.length > minLength);
// 正確用法
console.log(isLongerThan("hello", 3)); // true
// 錯誤用法將在型別檢查階段被攔截
// isLongerThan(123, "hi"); // ❌ TypeScript 編譯不通過
Zod 讓你像寫型別一樣寫驗證,將型別安全提升到運行階段,讓業務邏輯更可靠!
進階:複雜參數與回傳型別
你可以驗證更複雜的參數結構,例如物件、陣列,甚至多個參數:
const sumNumbers = z
.function()
.args(z.array(z.number()))
.returns(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()
.args(
z.object({
username: z.string().min(3),
password: z.string().min(8),
})
)
.returns(z.object({ id: z.string().uuid() }))
.implement(async ({ username, password }) => {
// 處理註冊邏輯...
return { id: crypto.randomUUID() };
});
// 呼叫時自動檢查型別
registerUser({ username: "calpa", password: "supersecret123" }).then(console.log);
// registerUser({ username: "ab", password: "123" }); // ❌ 參數驗證失敗
與 React、tRPC 等框架無縫整合
Zod 可完美整合於現代前後端框架,例如 React 與 tRPC。它讓你不僅能在前端開發中驗證表單資料、API 請求和回應,還能將型別安全從編譯時延伸到執行時。這種端到端的驗證,大幅提升資料正確性與應用穩定性,有效防止錯誤數據流入,顯著降低潛在風險。
React 結合 Zod 驗證表單資料範例
你可以使用 Zod 來驗證 React 表單資料,以下是一個簡單的範例:
import React, { useState } from "react";
import { z } from "zod";
const schema = z.object({
email: z.string().email({ message: "請輸入有效的電子郵件" }),
age: z
.number({ invalid_type_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.errors.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.string().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 的關鍵場景與實例
隨著 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 時間,更能大幅提升生產環境的穩定性。
結論
Zod 就像是 TypeScript 的資料守門員,讓你不再擔心資料結構與實際輸入脫節的問題。它橋接了開發者最常面對的兩端——靜態型別與動態資料,無論你面對的是表單、API、還是 AI 模型,都能幫你築起一道清晰又安全的資料牆。
對我來說,這不只是提升開發效率,更是讓 Vibe Coding 真正穩定運作的基礎。如果你也在追求高可靠性的 AI 專案工作流,Zod 絕對值得成為你的資料驗證首選。
如果你有 AI 專案、網站開發或技術整合需求,歡迎來信交流: partner@calpa.me
歡迎訂閱 Calpa 的頻道,一同將想像力化為可能: