Β· best practices

Why Write Validation Logic When Zod Can Do It Better?

Learn how to strengthen your TypeScript applications with Zod, a powerful schema validation library. Discover how Zod enables type-safe, runtime validation with minimal boilerplate, so you can save time and effort when developing API services and data-driven applications.

Zod is a powerful, TypeScript-first validation library that combines static type safety with runtime data validation. It’s especially useful when you want to ensure that incoming data matches your expected structure without writing repetitive boilerplate code.

Contents

Safe Validation with Result Types

If you prefer non-throwing validation, use safeParse. It returns a result object that separates success from failure:

const validation = UserSchema.safeParse(user);
 
if (validation.error) {
  console.error(validation.error.format());
} else {
  const user = validation.data;
  console.log(user.name);
}

This approach is useful in functional programming patterns, where you handle errors as data instead of using exceptions. It can be used for always returning Result Types.

Composing Zod Schemas

Zod supports reusable and composable schemas, allowing you to build complex validations from smaller building blocks:

import { z } from 'zod';
 
const AddressSchema = z.object({
  street: z.string(),
  city: z.stringw(),
  state: z.string(),
  postalCode: z.string(),
  country: z.string(),
});
 
export const UserSchema = z.object({
  address: AddressSchema,
  name: z.string().default('John Doe'),
});

Schemas like AddressSchema can be reused across multiple models, improving maintainability and consistency. The Zod validator is written in TypeScript itself and also supports features like Literal types and readonly properties.

Saving Time with Zod

Using Zod for this not only improves type safety and maintainability but also makes validation much easier and more reliable compared to manual checks.

Validation without Zod

In plain TypeScript, you're responsible for manually checking for missing properties using conditionals. You also need to define fallback values on your own and validate data types explicitly, which can quickly become tedious when working with nested data structures:

type User = {
  name: string;
  age: number;
};
 
async function fetchUser(): Promise<User> {
  const res = await fetch('https://api.example.com/user');
  const data = await res.json();
 
  if (!data.age) {
    throw new Error(`User has no age`);
  }
 
  return {
    name: data.name || 'John Doe',
    age: data.age,
  };
}

This kind of defensive programming can quickly become repetitive, error-prone, and difficult to maintain as your data structures grow in size and complexity.

Validation with Zod

Zod provides a much cleaner and declarative way to define and validate data shapes. It automatically infers the TypeScript type from the schema, reducing boilerplate and keeping your code DRY:

import { z } from 'zod';
 
const UserSchema = z.object({
  name: z.string().default('John Doe'),
  age: z.number(),
});
 
async function fetchUser() {
  const res = await fetch('https://api.example.com/user');
  const data = await res.json();
  return UserSchema.parse(data);
}
Back to Blog