TypeScript checks types during development, but it cannot validate data at runtime. For example, an API might return unexpected data, or a customer might submit an invalid phone number.
Zod is a TypeScript-first validation library that checks whether data matches a schema at runtime. It can also transform data and infer TypeScript types directly from schemas.
A schema describes the structure and validation rules of your data.
import { z } from "zod";const checkoutSchema = z.object({name: z.string().trim().min(2, "Name is too short"),phone: z.string().regex(/^0\d{9}$/,"Invalid phone number"),age: z.number().min(18),});type CheckoutForm = z.infer<typeof checkoutSchema>;
Here, z.object() defines an object schema, while z.string() and z.number() define the expected data types.
Common validation methods include:
.min() and .max() — Set minimum and maximum values or lengths..regex() — Validate a string against a regular expression..optional() — Allow a value to be undefined..nullable() — Allow a value to be null.z.infer<typeof checkoutSchema> automatically derives the TypeScript type from the schema, avoiding duplicate type definitions.
parse() and safeParse()Zod provides two main methods for validating data.
parse()
Use parse() when invalid data should throw a ZodError.
const data = checkoutSchema.parse(formValues);
If the data is invalid, execution throws an error. Otherwise, data contains the validated output.
safeParse()
Use safeParse() when you want to handle validation errors explicitly.
const result = checkoutSchema.safeParse(formValues);if (!result.success) {console.log(result.error.issues);return;}await createOrder(result.data);
safeParse() returns a result object with either success: true and validated data, or success: false and a ZodError.
For form validation, safeParse() is often convenient because invalid input can be handled without throwing an exception.
Validation is not always just about checking data. Sometimes, input needs to be cleaned before it is used.
`.trim()
Removes whitespace from the beginning and end of a string.
z.string().trim().parse(" Viet ");// "Viet"
.transform()
Converts validated input into a different output.
const phoneSchema = z.string().transform((value) => value.replace(/\s/g, "")).pipe(z.string().regex(/^0\d{9}$/));phoneSchema.parse("038 656 9374");// "0386569374"
refine()Use .refine() when built-in validators are not enough.
const passwordSchema = z.string().refine((value) => value.length >= 8,{ error: "Password must be at least 8 characters" });
For more complex validation, .superRefine() can report multiple issues and associate them with specific fields.
For example, it can validate that a password and its confirmation match.