Skip to content

Zod Validation

TypeScript ตรวจสอบ type ได้แค่ตอน compile — แต่ข้อมูลจาก API หรือ form เข้ามาตอน runtime Zod แก้ปัญหานี้โดยให้ทั้ง runtime validation และ TypeScript types ในตัวเดียว

import { z } from "zod";
// กำหนด schema — เป็นทั้ง validator และ type definition
const UserSchema = z.object({
id: z.number(),
name: z.string().min(1, "ชื่อห้ามว่าง"),
email: z.string().email("อีเมลไม่ถูกต้อง"),
age: z.number().min(0).max(150).optional(),
role: z.enum(["admin", "user", "editor"]),
});
// สร้าง TypeScript type จาก schema โดยอัตโนมัติ!
type User = z.infer<typeof UserSchema>;
// { id: number; name: string; email: string; age?: number; role: "admin" | "user" | "editor" }
// parse — throw error ถ้าไม่ผ่าน
try {
const user = UserSchema.parse({
id: 1,
name: "สมชาย",
email: "somchai@test.com",
role: "admin"
});
console.log(user); // type-safe User object
} catch (err) {
if (err instanceof z.ZodError) {
console.log(err.errors); // รายละเอียด validation errors
}
}
// safeParse — ไม่ throw error คืน result object แทน
const result = UserSchema.safeParse(unknownData);
if (result.success) {
console.log(result.data); // type: User
} else {
console.log(result.error.issues); // validation errors
}
// แยก schema ย่อยแล้วรวมกัน
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}$/, "รหัสไปรษณีย์ต้อง 5 หลัก"),
});
// Extend schema
const UserWithAddressSchema = UserSchema.extend({
address: AddressSchema,
phone: z.string().optional(),
});
// Pick / Omit เหมือน TypeScript utility types
const CreateUserSchema = UserSchema.omit({ id: true });
const UserPreviewSchema = UserSchema.pick({ id: true, name: true, role: true });
type CreateUserInput = z.infer<typeof CreateUserSchema>;
type UserPreview = z.infer<typeof UserPreviewSchema>;
const ContactFormSchema = z.object({
name: z.string().min(2, "ชื่อต้องมีอย่างน้อย 2 ตัวอักษร"),
email: z.string().email("รูปแบบอีเมลไม่ถูกต้อง"),
subject: z.string().min(5, "หัวข้อต้องมีอย่างน้อย 5 ตัวอักษร"),
message: z.string().min(10, "ข้อความต้องมีอย่างน้อย 10 ตัวอักษร"),
priority: z.enum(["low", "medium", "high"]).default("medium"),
});
type ContactForm = z.infer<typeof ContactFormSchema>;
// ใช้กับ form handler
function handleSubmit(formData: unknown): ContactForm | null {
const result = ContactFormSchema.safeParse(formData);
if (!result.success) {
const fieldErrors: Record<string, string> = {};
result.error.issues.forEach(issue => {
const field = issue.path[0] as string;
fieldErrors[field] = issue.message;
});
console.log("Validation errors:", fieldErrors);
return null;
}
return result.data;
}
const ConfigSchema = z.object({
port: z.string().transform(Number).pipe(z.number().min(1).max(65535)),
debug: z.string().transform(val => val === "true").default("false"),
apiUrl: z.string().url().default("http://localhost:3000"),
});
const config = ConfigSchema.parse({
port: "3000", // transform จาก string เป็น number
debug: "true", // transform จาก string เป็น boolean
// apiUrl ใช้ default
});
// config = { port: 3000, debug: true, apiUrl: "http://localhost:3000" }