Zod is the TypeScript-first schema validation library with 34K+ GitHub stars — it validates data at runtime AND infers TypeScript types automatically. Zero dependencies, 11KB.
Why Zod?
- TypeScript-first — define schema once, get types automatically
- Zero dependencies — runs anywhere JavaScript runs
- Composable — combine, extend, transform schemas
- Error messages — detailed, customizable error reporting
- Ecosystem — React Hook Form, tRPC, Drizzle, Hono all use Zod
Quick Start
npm install zod
import { z } from "zod";
// Define a schema
const UserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().min(18).max(120),
role: z.enum(["admin", "user", "moderator"]),
});
// Infer TypeScript type (no manual type definition!)
type User = z.infer<typeof UserSchema>;
// type User = { name: string; email: string; age: number; role: "admin" | "user" | "moderator" }
// Validate data
const result = UserSchema.safeParse({
name: "Alice",
email: "alice@example.com",
age: 30,
role: "admin",
});
if (result.success) {
console.log(result.data); // Fully typed as User
} else {
console.log(result.error.issues); // Detailed error info
}
Schema Types
// Primitives
const str = z.string();
const num = z.number();
const bool = z.boolean();
const date = z.date();
const bigint = z.bigint();
// String validations
const email = z.string().email();
const url = z.string().url();
const uuid = z.string().uuid();
const minMax = z.string().min(5).max(100);
const regex = z.string().regex(/^[a-z]+$/);
const trim = z.string().trim().toLowerCase();
// Number validations
const positive = z.number().positive();
const integer = z.number().int();
const range = z.number().min(0).max(100);
// Arrays
const tags = z.array(z.string()).min(1).max(10);
const uniqueIds = z.array(z.number()).nonempty();
// Enums
const status = z.enum(["active", "inactive", "banned"]);
// Unions
const response = z.union([
z.object({ status: z.literal("success"), data: z.any() }),
z.object({ status: z.literal("error"), message: z.string() }),
]);
// Discriminated union (faster)
const event = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("scroll"), offset: z.number() }),
z.object({ type: z.literal("keypress"), key: z.string() }),
]);
Transforms (Parse + Transform)
const DateString = z.string().transform((val) => new Date(val));
// Input: string → Output: Date
const CurrencyAmount = z.string()
.regex(/^\$[\d,]+\.?\d*$/)
.transform((val) => parseFloat(val.replace(/[$,]/g, "")));
// Input: "$1,234.56" → Output: 1234.56
const SearchParams = z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(10),
sort: z.enum(["asc", "desc"]).default("desc"),
q: z.string().optional(),
});
// Coerce converts URL params (strings) to proper types
const params = SearchParams.parse({
page: "3", // string → number 3
limit: "25", // string → number 25
});
Express.js Validation Middleware
import express from "express";
import { z, ZodError } from "zod";
function validate(schema: z.ZodSchema) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
errors: result.error.issues.map((i) => ({
field: i.path.join("."),
message: i.message,
})),
});
}
req.body = result.data;
next();
};
}
const CreateOrderSchema = z.object({
items: z.array(z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive(),
})).nonempty(),
shipping: z.object({
address: z.string().min(5),
city: z.string(),
zip: z.string().regex(/^\d{5}$/),
}),
couponCode: z.string().optional(),
});
app.post("/orders", validate(CreateOrderSchema), (req, res) => {
// req.body is fully validated and typed
const order = createOrder(req.body);
res.status(201).json(order);
});
React Hook Form Integration
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const SignupSchema = z.object({
username: z.string().min(3).max(20),
email: z.string().email(),
password: z.string().min(8).regex(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/,
"Must contain uppercase, lowercase, and number"
),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"],
});
type SignupForm = z.infer<typeof SignupSchema>;
function SignupPage() {
const { register, handleSubmit, formState: { errors } } = useForm<SignupForm>({
resolver: zodResolver(SignupSchema),
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("username")} />
{errors.username && <span>{errors.username.message}</span>}
<input {...register("email")} type="email" />
{errors.email && <span>{errors.email.message}</span>}
<input {...register("password")} type="password" />
{errors.password && <span>{errors.password.message}</span>}
<input {...register("confirmPassword")} type="password" />
{errors.confirmPassword && <span>{errors.confirmPassword.message}</span>}
<button type="submit">Sign Up</button>
</form>
);
}
Environment Variables Validation
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(20),
PORT: z.coerce.number().default(3000),
REDIS_URL: z.string().url().optional(),
SMTP_HOST: z.string().optional(),
SMTP_PORT: z.coerce.number().optional(),
});
// Validate at startup — fail fast if env is wrong
export const env = EnvSchema.parse(process.env);
Zod vs Joi vs Yup vs Valibot
| Feature | Zod | Joi | Yup | Valibot |
|---|---|---|---|---|
| Size | 11KB | 50KB+ | 25KB | 1KB |
| TypeScript | First-class | @types | Built-in | First-class |
| Type inference | Automatic | No | Partial | Automatic |
| Tree-shakeable | No | No | No | Yes |
| Transforms | Yes | Yes | Yes | Yes |
| Async validation | Yes | Yes | Yes | Yes |
| Ecosystem | Huge | Large | Large | Growing |
Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.
Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.
Top comments (0)