TypeScript types disappear at runtime. That means your API inputs, form data, and environment variables are unvalidated in production. Zod fixes this — define a schema once, get both TypeScript types AND runtime validation.
What Zod Gives You for Free
- Schema-first validation — define once, validate everywhere
-
Type inference —
z.infer<typeof schema>gives you the TypeScript type - Composable — merge, extend, pick, omit schemas
- Transforms — parse and transform data in one step
- Error messages — detailed, customizable validation errors
- Zero dependencies — 13KB minified
- Works everywhere — browser, Node.js, Deno, Bun, Cloudflare Workers
Quick Start
npm install zod
Basic Schemas
import { z } from 'zod';
// Primitive types
const name = z.string().min(2).max(100);
const age = z.number().int().positive().max(150);
const email = z.string().email();
const isActive = z.boolean();
// Object schemas
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().int().positive(),
role: z.enum(['admin', 'user', 'moderator']),
bio: z.string().optional(),
tags: z.array(z.string()).max(10),
});
// Infer the TypeScript type automatically
type User = z.infer<typeof UserSchema>;
// = { name: string; email: string; age: number; role: 'admin' | 'user' | 'moderator'; bio?: string; tags: string[] }
Validation
// Safe parse (returns result object)
const result = UserSchema.safeParse(unknownData);
if (result.success) {
console.log(result.data.name); // Fully typed!
} else {
console.log(result.error.issues); // Detailed error messages
}
// Parse (throws on invalid)
try {
const user = UserSchema.parse(unknownData);
console.log(user.email); // Typed
} catch (e) {
if (e instanceof z.ZodError) {
console.log(e.issues);
}
}
API Request Validation
// Express/Fastify middleware
const CreateUserBody = z.object({
name: z.string().min(2),
email: z.string().email(),
password: z.string().min(8).regex(/[A-Z]/, 'Must contain uppercase'),
});
app.post('/api/users', (req, res) => {
const result = CreateUserBody.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() });
}
// result.data is typed as { name: string; email: string; password: string }
createUser(result.data);
});
Environment Variables
const EnvSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(20),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test']),
REDIS_URL: z.string().url().optional(),
});
export const env = EnvSchema.parse(process.env);
// env.PORT is number (coerced from string)
// env.NODE_ENV is 'development' | 'production' | 'test'
Transforms
const DateString = z.string().transform((str) => new Date(str));
const date = DateString.parse('2026-03-30'); // Returns Date object
const slug = z.string().transform((s) => s.toLowerCase().replace(/\s+/g, '-'));
slug.parse('Hello World'); // 'hello-world'
Schema Composition
const BaseUser = z.object({
name: z.string(),
email: z.string().email(),
});
// Extend
const AdminUser = BaseUser.extend({
role: z.literal('admin'),
permissions: z.array(z.string()),
});
// Pick
const UserEmail = BaseUser.pick({ email: true });
// Merge
const UserWithProfile = BaseUser.merge(z.object({
bio: z.string(),
avatar: z.string().url(),
}));
// Discriminated unions
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() }),
]);
Zod Ecosystem
- tRPC — uses Zod for input validation
-
React Hook Form —
@hookform/resolvers/zodfor form validation - Next.js — Server Actions use Zod
- Astro — content collections use Zod
- Hono — built-in Zod validator middleware
The Verdict
Zod bridges the gap between TypeScript's compile-time types and runtime reality. Define a schema once, get validation AND types. It's become the standard for TypeScript validation — and for good reason.
Need help building production web scrapers or data pipelines? I build custom solutions. Reach out: spinov001@gmail.com
Check out my awesome-web-scraping collection — 400+ tools for extracting web data.
Top comments (0)