Zod Dari Dasar Hingga Mahir: Panduan Lengkap Semua Jenis Validasi
Validasi adalah fondasi dari sistem yang sehat. Tanpa validasi yang baik, data yang masuk ke sistem bisa menyebabkan bug, inkonsistensi, bahkan celah keamanan.
Zod adalah schema validation library berbasis TypeScript-first yang memungkinkan kita mendefinisikan struktur data sekaligus mendapatkan type inference otomatis.
Artikel ini membahas Zod dari dasar hingga mahir, termasuk seluruh jenis validasi yang umum digunakan, lengkap dengan contoh.
Apa Itu Zod?
Zod adalah library untuk:
- Mendefinisikan schema data
- Melakukan validasi runtime
- Menghasilkan type TypeScript secara otomatis
Contoh paling sederhana:
import { z } from "zod";
const schema = z.string();
schema.parse("hello"); // valid
schema.parse(123); // error
Jika validasi gagal, Zod akan melempar error.
Primitive Types
String
z.string();
Validasi tambahan:
z.string().min(3);
z.string().max(10);
z.string().length(5);
z.string().email();
z.string().url();
z.string().uuid();
z.string().regex(/^[A-Z]+$/);
z.string().startsWith("A");
z.string().endsWith("Z");
Number
z.number();
Validasi tambahan:
z.number().min(1);
z.number().max(100);
z.number().int();
z.number().positive();
z.number().negative();
z.number().nonnegative();
z.number().multipleOf(5);
Boolean
z.boolean();
Date
z.date();
Dengan batasan:
z.date().min(new Date("2024-01-01"));
z.date().max(new Date());
Object Schema
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
Infer type otomatis:
type User = z.infer<typeof userSchema>;
Optional, Nullable, Default
Optional
z.string().optional();
Nullable
z.string().nullable();
Default Value
z.string().default("anonymous");
Array Validation
z.array(z.string());
Dengan constraint:
z.array(z.string()).min(1);
z.array(z.string()).max(5);
z.array(z.number()).nonempty();
Enum dan Literal
Literal
z.literal("admin");
Enum
z.enum(["admin", "user", "guest"]);
Union dan Discriminated Union
Union
z.union([z.string(), z.number()]);
Discriminated Union
z.discriminatedUnion("type", [
z.object({ type: z.literal("a"), value: z.string() }),
z.object({ type: z.literal("b"), value: z.number() }),
]);
Digunakan ketika struktur berubah berdasarkan satu field.
Nested Object
const schema = z.object({
user: z.object({
name: z.string(),
address: z.object({
city: z.string(),
}),
}),
});
Record (Dynamic Key Object)
z.record(z.string());
Dengan key dan value type:
z.record(z.string(), z.number());
Refinement (Custom Validation)
refine()
z.string().refine((val) => val.includes("@"), {
message: "Harus mengandung @",
});
superRefine()
z.object({
password: z.string(),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Password tidak sama",
path: ["confirmPassword"],
});
}
});
Transform
Digunakan untuk mengubah data setelah validasi.
z.string().transform((val) => val.trim());
Contoh parsing number dari string:
z.string().transform((val) => Number(val));
Preprocess
Digunakan untuk memodifikasi input sebelum validasi.
z.preprocess((val) => Number(val), z.number());
Cocok untuk form input HTML.
Intersection
Menggabungkan dua schema:
const a = z.object({ name: z.string() });
const b = z.object({ age: z.number() });
const merged = z.intersection(a, b);
Partial, Pick, Omit, Extend
Partial
userSchema.partial();
Pick
userSchema.pick({ name: true });
Omit
userSchema.omit({ age: true });
Extend
userSchema.extend({ role: z.string() });
Strict vs Passthrough
Strict
z.object({ name: z.string() }).strict();
Menolak property tambahan.
Passthrough
z.object({ name: z.string() }).passthrough();
Mengizinkan property tambahan.
Safe Parse
Tanpa throw error:
const result = schema.safeParse(data);
if (!result.success) {
console.log(result.error);
}
Error Handling dan Custom Message
z.string().min(3, { message: "Minimal 3 karakter" });
Global error map juga bisa dikustomisasi.
Async Validation
z.string().refine(async (val) => {
const exists = await checkUser(val);
return !exists;
}, {
message: "User sudah ada",
});
Gunakan parseAsync() untuk menjalankannya.
Branding dan Nominal Typing
const UserId = z.string().brand("UserId");
Digunakan untuk type safety tingkat lanjut.
Best Practice
- Pisahkan schema domain dan schema form
- Gunakan discriminatedUnion untuk struktur kompleks
- Hindari logic validasi di UI
- Manfaatkan inference type
- Gunakan safeParse untuk boundary layer (API, service)
Penutup
Zod bukan sekadar library validasi, tetapi alat untuk menjaga integritas sistem.
Dengan memahami seluruh fitur yang tersedia — dari primitive validation hingga discriminated union dan transform — kita dapat membangun sistem yang:
- Type-safe
- Konsisten
- Mudah di-maintain
- Scalable untuk kompleksitas masa depan
Semakin kompleks aplikasi Anda, semakin penting schema yang kuat sebagai single source of truth.