๐Ÿ›ก๏ธ Zod Playground โ€“ Mastering Type-safe Validation in TypeScript

Abheeshta PAbheeshta P
3 min read

Zod is a TypeScript-first schema declaration and validation library. This article demonstrates the usage of Zod for validating and parsing data structures, building form validators, discriminated unions, custom errors, and much more.

๐Ÿ“ฆ Features Covered

  • โœ… Schema creation and type inference

  • โœ… Safe and unsafe parsing

  • โœ… Enums and literals

  • โœ… Object extensions and transformations

  • โœ… Discriminated unions

  • โœ… Record vs Map

  • โœ… Default values

  • โœ… Optional, nullable, and nullish handling

  • โœ… Custom error messages using errorMap

  • โœ… Promise schema

  • โœ… Nested schemas and advanced schema composition

  • โœ… Clean error reporting using zod-validation-error

๐Ÿ”ง Basic Schema and Type Inference

const UserSchema = z.object({
  username: z.string(),
}).strict();

type userType = z.infer<typeof UserSchema>;
  • strict() ensures no extra fields are allowed.

  • z.infer auto-generates a TypeScript type from the schema.

๐Ÿ” Parsing Data

1. parse() โ€“ throws error if invalid

UserSchema.parse({ username: "Kajal" });

2. safeParse() โ€“ returns success boolean and error

UserSchema.safeParse({ username: "Kajal" });

๐Ÿง  Enum & Union

Using native enum:

enum JobPref {
  Remote,
  Onsite,
  Hybrid,
}
z.nativeEnum(JobPref);

Using string literals:

z.enum(["Remote", "Onsite", "Hybrid"]);

๐Ÿงฉ Advanced Object Schema

const userSchema2 = z.object({
  username: z.string().min(5).max(100),
  age: z.number().positive().default(Math.random()),
  birthday: z.date().optional(),
  isProgrammer: z.boolean().default(true),
  website: z.string().url().nullable(),
  hobbies: z.array(z.string()).nonempty().min(1).max(4),
  workPreference: z.enum(["Remote", "Onsite", "Hybrid"]).optional(),
}).passthrough();

Notes:

  • .optional() โ€“ field can be undefined.

  • .nullable() โ€“ field can be null.

  • .nonempty() โ€“ array cannot be empty.

  • .default() โ€“ sets default value if not provided.

  • .passthrough() โ€“ allows extra fields.

  • .strict() โ€“ blocks any extra fields.

๐Ÿงช Schema Utilities

  • pick() โ€“ include only some fields

  • omit() โ€“ remove specific fields

  • deepPartial() โ€“ optional deep fields

  • extend() โ€“ add new fields

  • partial() โ€“ all fields optional

๐Ÿ”— Tuple and Set

z.tuple([z.date(), z.number().gt(25)]).rest(z.string()); // fixed and variable parts
z.set(z.number()); // unique array values

๐Ÿ”„ Discriminated Union

Perfect for handling polymorphic data:

const textSchema = z.object({ type: z.literal("text"), content: z.string() });
const imageSchema = z.object({ type: z.literal("image"), url: z.string().url(), alt: z.string() });

const messageSchema = z.discriminatedUnion("type", [textSchema, imageSchema]);

This validates objects based on a type field and automatically picks the right schema.

๐Ÿ—ƒ๏ธ Record vs Map

Record (like object with dynamic keys)

z.record(z.number()); // key: string, val: number
z.record(z.string(), z.number()); // key: string, val: number

Map (JS Map object)

z.map(z.string(), z.number()); // key: string, val: number

Use record for plain JS objects and map when working with Map instances.

๐Ÿ” Refinement & Custom Errors

const brandEmail = z
  .string()
  .email()
  .refine(val => val.endsWith("kajalpiyaaa.in"), {
    message: "Email must end with @kajalpiyaaa.in",
  });

Use refine() to add custom validation logic.

๐Ÿงผ Clean Error Output

import { fromZodError } from "zod-validation-error";

const result = brandEmail.safeParse(email);
if (!result.success) {
  console.log(fromZodError(result.error));
}


This gives **pretty error messages** instead of long error stacks.



## โŒ Custom Error Message Globally

```ts
const Schema = z.array(z.string(), {
  errorMap: () => ({ message: "yo it's a string!" }),
});

Use errorMap to define schema-specific error messages.

๐Ÿงต Promise Handling

const userAsync = z.promise(z.string());
// Ensures the resolved value is a string

๐Ÿง  Best Practices

  • Use .infer<typeof Schema> to keep DRY in types.

  • .safeParse() is great for graceful error handling.

  • Prefer zod-validation-error for clean dev experience.

  • Chain validations (like .min(), .default()) as needed.

  • Use discriminated unions for clear type-safe API schemas.

๐Ÿ Conclusion

This article showcases most of the core and advanced Zod features, making it an ideal reference for:

  • Form validation

  • API validation

  • Config object schema enforcement

  • Type-safe client-server communication

Thanks to web dev simplified!

0
Subscribe to my newsletter

Read articles from Abheeshta P directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abheeshta P
Abheeshta P

I am a Full-stack dev turning ideas into sleek, functional experiences ๐Ÿš€. I am passionate about AI, intuitive UI/UX, and crafting user-friendly platforms . I am always curious โ€“ from building websites to diving into machine learning and under the hood workings โœจ. Next.js, Node.js, MongoDB, and Tailwind are my daily tools. I am here to share dev experiments, lessons learned, and the occasional late-night code breakthroughs. Always evolving, always building.