The Validation Problem
Your API receives a request body. TypeScript tells you it is type-safe. But TypeScript types disappear at runtime. That user: User could be anything — a string, null, or {hack: true}.
You need runtime validation. And it should match your TypeScript types.
Zod: Schema Validation That Generates Types
Zod is a TypeScript-first schema validation library. Define the schema once, get both runtime validation AND TypeScript types.
Basic Usage
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional()
})
// TypeScript type is GENERATED from the schema
type User = z.infer<typeof UserSchema>
// { name: string; email: string; age?: number }
// Runtime validation
const result = UserSchema.safeParse(requestBody)
if (!result.success) {
console.log(result.error.issues)
// [{ code: 'invalid_string', message: 'Invalid email', path: ['email'] }]
} else {
const user = result.data // Fully typed as User
}
One source of truth. No type/validation drift.
Why 30M+ Weekly Downloads
1. API Input Validation
app.post('/users', (req, res) => {
const result = UserSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.flatten() })
}
// result.data is guaranteed to match User type
createUser(result.data)
})
2. Environment Variable Validation
const EnvSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().default(3000),
NODE_ENV: z.enum(['development', 'production', 'test'])
})
export const env = EnvSchema.parse(process.env)
// Crashes at startup if env vars are wrong — not at 3 AM in production
3. Form Validation (React)
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
const form = useForm({
resolver: zodResolver(UserSchema)
})
// Form validation matches API validation — same schema
4. API Response Validation
const ApiResponse = z.object({
data: z.array(UserSchema),
total: z.number()
})
const response = await fetch('/api/users')
const validated = ApiResponse.parse(await response.json())
// If the API changes shape, you know immediately — not in a bug report
Zod vs Alternatives
| Feature | Zod | Joi | Yup |
|---|---|---|---|
| TypeScript-first | Yes | No | Partial |
| Type inference | Full | Manual | Partial |
| Bundle size | 13KB | 150KB | 40KB |
| Ecosystem | Huge | Legacy | Moderate |
| Error messages | Structured | Strings | Strings |
The Zod Ecosystem
- tRPC: Uses Zod for end-to-end type-safe APIs
- React Hook Form: Official Zod resolver
- Next.js: Server Actions use Zod for validation
- Astro: Content collections use Zod schemas
- Drizzle: Generates Zod schemas from database tables
Install
npm install zod
Zero dependencies. 13KB gzipped. Works everywhere JavaScript runs.
Building data pipelines? I maintain 88+ web scrapers on Apify. Extract data from Reddit, Trustpilot, HN, Google News — structured JSON/CSV ready for your app. Custom scrapers: spinov001@gmail.com
Top comments (0)