Zod Validation
Zod Validation
Section titled “Zod Validation”TypeScript ตรวจสอบ type ได้แค่ตอน compile — แต่ข้อมูลจาก API หรือ form เข้ามาตอน runtime Zod แก้ปัญหานี้โดยให้ทั้ง runtime validation และ TypeScript types ในตัวเดียว
Schema พื้นฐาน
Section titled “Schema พื้นฐาน”import { z } from "zod";
// กำหนด schema — เป็นทั้ง validator และ type definitionconst 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" }// JS ต้องเขียน validation เอง — ไม่มี type inferencefunction validateUser(data) { if (!data.name || typeof data.name !== "string") throw new Error("Invalid name"); if (!data.email?.includes("@")) throw new Error("Invalid email"); return data;}from pydantic import BaseModel, EmailStrclass User(BaseModel): id: int name: str email: EmailStr role: str# Pydantic ทำ runtime validation คล้าย Zod-- SQL ใช้ constraints สำหรับ validationCREATE TABLE users ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL CHECK (length(name) > 0), email VARCHAR(255) NOT NULL CHECK (email LIKE '%@%.%'), role VARCHAR(20) CHECK (role IN ('admin', 'user', 'editor')));Runtime Validation
Section titled “Runtime Validation”// 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 Composition
Section titled “Schema Composition”// แยก schema ย่อยแล้วรวมกันconst AddressSchema = z.object({ street: z.string(), city: z.string(), zipCode: z.string().regex(/^\d{5}$/, "รหัสไปรษณีย์ต้อง 5 หลัก"),});
// Extend schemaconst UserWithAddressSchema = UserSchema.extend({ address: AddressSchema, phone: z.string().optional(),});
// Pick / Omit เหมือน TypeScript utility typesconst 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>;Form Validation
Section titled “Form Validation”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 handlerfunction 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;}Transform และ Default
Section titled “Transform และ Default”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" }