Why Zod
Zod validates data AND gives you TypeScript types from schemas. Define once, validate at runtime, get types at compile time. No more duplicating types and validation logic.
Install
npm install zod
Basic Schemas
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().min(18).max(120),
role: z.enum(['admin', 'user', 'moderator']),
bio: z.string().optional(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age: number; role: 'admin'|'user'|'moderator'; bio?: string }
const user = UserSchema.parse({
name: 'Alice',
email: 'alice@example.com',
age: 30,
role: 'admin',
});
API Validation
import express from 'express';
import { z } from 'zod';
const CreateOrderSchema = z.object({
productId: z.string().uuid(),
quantity: z.number().int().positive(),
coupon: z.string().optional(),
});
app.post('/orders', (req, res) => {
const result = CreateOrderSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
const order = result.data; // Fully typed!
// ...
});
Transform and Refine
const DateSchema = z.string().transform((s) => new Date(s));
const PasswordSchema = z.string()
.min(8)
.refine((pw) => /[A-Z]/.test(pw), 'Must contain uppercase')
.refine((pw) => /[0-9]/.test(pw), 'Must contain number');
const PaginationSchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
});
Key Features
- Type inference — z.infer gives TypeScript types from schemas
- Runtime validation — parse() and safeParse()
- Composable — .extend(), .merge(), .pick(), .omit()
- Transforms — coerce, transform, preprocess
- Error formatting — flatten(), format()
- Zero dependencies — tiny bundle
Resources
Need to extract API schemas, validation patterns, or TypeScript type data? Check out my Apify tools or email spinov001@gmail.com for custom solutions.
Top comments (0)