DEV Community

Alex Spinov
Alex Spinov

Posted on

Zod Has a Free Schema Library That Makes TypeScript Validation Effortless

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 inferencez.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
Enter fullscreen mode Exit fullscreen mode

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[] }
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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() }),
]);
Enter fullscreen mode Exit fullscreen mode

Zod Ecosystem

  • tRPC — uses Zod for input validation
  • React Hook Form@hookform/resolvers/zod for 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)