Zod: Validasi Data TypeScript yang Seharusnya Sudah Anda Pakai dari Dulu
Zod adalah schema validation library yang mengubah cara developer TypeScript handle input validation. Type-safe, composable, dan integrasinya mulus dengan Next.js, React Hook Form, dan tRPC.
Muhamad Putra Aulia Hidayat
Zod: Validasi Data yang Type-Safe dan Ekspresif
Kalau kamu masih validasi input secara manual dengan if-else berantai, ini saatnya beralih ke Zod. Library ini mengubah validasi dari pekerjaan membosankan jadi bagian yang menyenangkan dari development.
Kenapa Zod?
- Type inference — schema otomatis menghasilkan TypeScript type
- Composable — schema bisa dikombinasikan dan di-reuse
- Zero dependencies — ringan dan tanpa bloat
- Great error messages — pesan error yang informatif
- Ekosistem luas — terintegrasi dengan React Hook Form, tRPC, SWR, dll
Dasar-Dasar Zod
import { z } from "zod"
// Schema sederhana
const EmailSchema = z.string().email("Email tidak valid")
const AgeSchema = z.number().int().min(17).max(100)
// Object schema
const UserSchema = z.object({
name: z.string().min(2, "Nama minimal 2 karakter").max(50),
email: z.string().email("Email tidak valid"),
phone: z.string().regex(/^08[0-9]{8,11}$/, "Format HP tidak valid").optional(),
age: z.number().int().min(17).max(100),
role: z.enum(["admin", "user", "moderator"]).default("user"),
})
// Infer TypeScript type secara otomatis
type User = z.infer<typeof UserSchema>
// Sama dengan:
// type User = {
// name: string;
// email: string;
// phone?: string;
// age: number;
// role: "admin" | "user" | "moderator";
// }
Parse vs SafeParse
// parse() - throw error kalau invalid
try {
const user = UserSchema.parse(formData)
// user sudah pasti valid dan type-safe
} catch (error) {
if (error instanceof z.ZodError) {
console.log(error.flatten().fieldErrors)
}
}
// safeParse() - return result object, tidak throw
const result = UserSchema.safeParse(formData)
if (!result.success) {
const errors = result.error.flatten().fieldErrors
return { errors }
}
const user = result.data // Type: User
Schema Lanjutan
// Array
const TagsSchema = z.array(z.string()).min(1).max(10)
// Union
const IdSchema = z.union([z.string().uuid(), z.number().int().positive()])
// Transform - ubah data saat validasi
const PriceSchema = z.string().transform(val => parseFloat(val))
// Input: "25000" → Output: 25000 (number)
// Refine - validasi custom
const PasswordSchema = z.string()
.min(8)
.refine(
(val) => /[A-Z]/.test(val) && /[0-9]/.test(val),
"Password harus mengandung huruf kapital dan angka"
)
// SuperRefine - validasi yang bergantung pada field lain
const SignupSchema = z.object({
password: z.string().min(8),
confirmPassword: z.string()
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Password tidak sama",
path: ["confirmPassword"]
})
}
})
Integrasi dengan React Hook Form
"use client"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
const ContactSchema = z.object({
name: z.string().min(2, "Nama minimal 2 karakter"),
email: z.string().email("Email tidak valid"),
message: z.string().min(20, "Pesan minimal 20 karakter"),
})
type ContactForm = z.infer<typeof ContactSchema>
export function ContactForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm<ContactForm>({
resolver: zodResolver(ContactSchema)
})
const onSubmit = async (data: ContactForm) => {
// data sudah pasti valid dan type-safe
await submitContact(data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="Nama" />
{errors.name && <p className="text-red-500">{errors.name.message}</p>}
<input {...register("email")} placeholder="Email" />
{errors.email && <p className="text-red-500">{errors.email.message}</p>}
<textarea {...register("message")} placeholder="Pesan" />
{errors.message && <p className="text-red-500">{errors.message.message}</p>}
<button type="submit" disabled={isSubmitting}>Kirim</button>
</form>
)
}
Validasi di API Route
// app/api/products/route.ts
import { z } from "zod"
const CreateProductSchema = z.object({
name: z.string().min(3).max(100),
price: z.number().positive("Harga harus lebih dari 0"),
stock: z.number().int().min(0),
category_id: z.string().uuid("Category ID tidak valid"),
description: z.string().max(1000).optional(),
})
export async function POST(req: Request) {
const body = await req.json()
const result = CreateProductSchema.safeParse(body)
if (!result.success) {
return Response.json(
{
error: "Validation failed",
details: result.error.flatten().fieldErrors
},
{ status: 400 }
)
}
const product = await createProduct(result.data)
return Response.json(product, { status: 201 })
}
Zod adalah salah satu library yang setelah kamu pakai, tidak akan mau kembali ke cara lama.
Newsletter Digital Uptime
Tips teknologi & bisnis mingguan
Bergabung dengan 2,500+ subscriber yang mendapatkan insight teknologi, tutorial development, dan tips bisnis digital langsung ke inbox mereka setiap minggu.