Zod Dari Dasar Hingga Mahir: Panduan Lengkap Semua Jenis Validasi
3 min read

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

  1. Pisahkan schema domain dan schema form
  2. Gunakan discriminatedUnion untuk struktur kompleks
  3. Hindari logic validasi di UI
  4. Manfaatkan inference type
  5. 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.