Zod is a TypeScript-first schema validation library. It lets you define schemas once and get both runtime validation and TypeScript types — eliminating the duplication between types and validation.
Installation
npm install zod
Basic Schemas
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().positive().max(150),
role: z.enum(["admin", "user", "moderator"]),
bio: z.string().optional()
});
// Infer TypeScript type
type User = z.infer<typeof userSchema>;
// Validate
const result = userSchema.safeParse({
name: "Alex",
email: "alex@dev.com",
age: 28,
role: "admin"
});
if (result.success) {
console.log(result.data.name); // Fully typed!
} else {
console.log(result.error.issues); // Detailed error messages
}
Advanced Types
// Arrays and tuples
const tags = z.array(z.string()).min(1).max(10);
const pair = z.tuple([z.string(), z.number()]);
// Unions and discriminated unions
const result = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.any() }),
z.object({ status: z.literal("error"), message: z.string() })
]);
// Records
const config = z.record(z.string(), z.unknown());
// Recursive types
const categorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(categorySchema)
})
);
Transforms
const stringToNumber = z.string().transform(Number);
const result = stringToNumber.parse("42"); // 42 (number)
const dateString = z.string().transform(s => new Date(s));
// Coercion
const coerced = z.coerce.number(); // "42" -> 42
const coercedDate = z.coerce.date(); // "2026-01-01" -> Date
API Validation
const createPostSchema = z.object({
body: z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
tags: z.array(z.string()).default([])
}),
params: z.object({}),
query: z.object({
draft: z.coerce.boolean().default(false)
})
});
// In Express/Fastify handler
const validated = createPostSchema.parse({
body: req.body,
params: req.params,
query: req.query
});
Error Formatting
const result = userSchema.safeParse(badData);
if (!result.success) {
const formatted = result.error.format();
console.log(formatted.email?._errors); // ["Invalid email"]
console.log(result.error.flatten()); // { fieldErrors: { email: ["Invalid email"] } }
}
Need to extract or automate web content at scale? Check out my web scraping tools on Apify — no coding required. Or email me at spinov001@gmail.com for custom solutions.
Top comments (0)