Zod is a TypeScript-first schema validation library. Define a schema once and get runtime validation, static types, and transformation — all with zero dependencies.
Why Zod?
- TypeScript-first: Schemas ARE your types
- Zero dependencies: 52KB bundle
- Composable: Chain, merge, extend schemas
- Ecosystem: Works with tRPC, React Hook Form, Hono, Astro
- Error messages: Human-readable by default
- Transforms: Parse and transform in one step
Install
npm install zod
Basic Schemas
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
age: z.number().int().positive().optional(),
role: z.enum(['admin', 'user', 'moderator']),
tags: z.array(z.string()).default([]),
});
// Infer TypeScript type from schema
type User = z.infer<typeof userSchema>;
// { name: string; email: string; age?: number; role: 'admin' | 'user' | 'moderator'; tags: string[] }
// Validate
const result = userSchema.safeParse(input);
if (result.success) {
console.log(result.data); // Typed as User
} else {
console.log(result.error.issues); // Detailed errors
}
Transformations
const priceSchema = z.string()
.transform(val => parseFloat(val))
.pipe(z.number().positive());
priceSchema.parse('29.99'); // 29.99 (number)
priceSchema.parse('-5'); // throws: Number must be positive
API Request Validation
const createPostSchema = z.object({
title: z.string().min(1).max(200).trim(),
body: z.string().min(10),
published: z.boolean().default(false),
tags: z.array(z.string()).max(5).optional(),
});
// Express middleware
app.post('/api/posts', (req, res) => {
const result = createPostSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
// result.data is fully typed
const post = await db.post.create({ data: result.data });
res.json(post);
});
Discriminated Unions
const eventSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('click'), x: z.number(), y: z.number() }),
z.object({ type: z.literal('scroll'), position: z.number() }),
z.object({ type: z.literal('input'), value: z.string(), field: z.string() }),
]);
Environment Variables
const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.string().transform(Number).pipe(z.number().int().positive()),
NODE_ENV: z.enum(['development', 'production', 'test']),
API_KEY: z.string().min(32),
});
export const env = envSchema.parse(process.env);
Recursive Schemas
type Category = {
name: string;
children: Category[];
};
const categorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(categorySchema),
})
);
Real-World Use Case
A team added Zod to their Express API. In the first week, input validation caught 23 malformed requests that had been silently causing database errors. They also eliminated 400 lines of manual validation code.
Need to automate data collection? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.
Top comments (0)