DEV Community

Alex Spinov
Alex Spinov

Posted on

Zod Has a Free Validation Library That Replaces Joi, Yup, and io-ts — TypeScript-First Schema Validation

The Validation Problem

You define a TypeScript type. Then you write a Joi schema. Then you realize they're out of sync. Your API accepts data that crashes your app.

Zod defines the schema once and infers the TypeScript type. Single source of truth.

What Zod Gives You

Schema = Type

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 INFERRED — no manual interface needed
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age?: number }
Enter fullscreen mode Exit fullscreen mode

Change the schema → the type updates automatically.

Parse, Don't Validate

// Throws on invalid data
const user = UserSchema.parse(requestBody);
// user is fully typed as User

// Or get errors without throwing
const result = UserSchema.safeParse(requestBody);
if (result.success) {
  console.log(result.data.name); // typed
} else {
  console.log(result.error.issues); // detailed errors
}
Enter fullscreen mode Exit fullscreen mode

API Request Validation

const CreatePostSchema = z.object({
  title: z.string().min(1).max(200),
  body: z.string().min(10),
  tags: z.array(z.string()).max(5),
  publishedAt: z.string().datetime().optional(),
});

app.post('/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 and validated
  createPost(result.data);
});
Enter fullscreen mode Exit fullscreen mode

Transform Data

const StringToNumber = z.string().transform(Number);
const result = StringToNumber.parse('42'); // 42 (number)

const Trimmed = z.string().trim().toLowerCase();
const email = Trimmed.parse('  User@Email.COM  '); // 'user@email.com'
Enter fullscreen mode Exit fullscreen mode

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'), offset: z.number() }),
  z.object({ type: z.literal('keypress'), key: z.string() }),
]);
Enter fullscreen mode Exit fullscreen mode

Works Everywhere

Zod works with: React Hook Form, tRPC, Next.js Server Actions, Remix, Astro, Express, Fastify, Hono — any TypeScript project.

Quick Start

npm install zod
Enter fullscreen mode Exit fullscreen mode

Zero dependencies. 14KB minified. TypeScript required.

Why This Matters

Every API boundary needs validation. Zod makes validation and TypeScript types the same thing. Write it once, get safety everywhere.


Need validated data from external sources? Check out my web scraping actors on Apify Store — structured, clean data for your schemas. For custom solutions, email spinov001@gmail.com.

Top comments (0)