8 minutes read

Introducing Zod: The TypeScript Library That'll Keep Your Data in Check

Are you tired of writing validation code for your TypeScript projects? Enter Zod (opens in a new tab), the TypeScript-first schema declaration and validation library that makes writing and validating schemas a breeze.

As software developers, we know that working with data can be challenging, especially when it comes to ensuring that the data is valid and consistent throughout our codebase. This is where schema declaration and validation libraries come into play, and today I'm going to introduce you to one of the best: Zod.

Zod is a TypeScript-first schema declaration and validation library that can help you keep your data in check. It provides a powerful and flexible way to declare data schemas, validate data against those schemas, and handle errors and feedback. Whether you're working on a small personal project or a large enterprise application, Zod can help you write cleaner, more reliable code and catch errors early in the development process.

In this article, I'm going to introduce you to Zod and its primary use case as a schema declaration and validation library for TypeScript. We'll discuss how Zod can help you keep your data consistent, why you should use it, and how it works. Whether you're a beginner or an experienced TypeScript developer, this article will give you a solid understanding of what Zod is and how it can benefit your projects. So, let's dive in!

What is Zod

So, what exactly is Zod? In short, it's a TypeScript-first schema declaration and validation library that allows you to define the shape and constraints of your data, and validate that data against those constraints. Think of it as a way to define a contract for your data, and ensure that any data that you receive or send out conforms to that contract.

One of the things I love about Zod is how easy it is to get started with. To declare a schema with Zod, you simply import the z function from the Zod library and call it with the schema definition. For example, let's say we want to define a schema for a user object with a name, email, and age. Here's what that would look like:

import { z } from "zod";
 
const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().int().positive(),
});

Once we have our schema defined, we can use it to validate any data that we receive. For example, let's say we receive a user object from an API:

const user = {
  name: "John Doe",
  email: "john@example.com",
  age: 30,
};
 
userSchema.parse(user); // this will pass validation

We can use the parse method to validate the user object against our schema. If the object passes validation, we'll get back the same object. If it fails validation, Zod will throw an error with a helpful message telling us what went wrong.

That's just a brief overview of what Zod is and how it works. In the next section, we'll take a closer look at some of the benefits of using Zod for data validation in TypeScript.

Why Use Zod

While TypeScript provides some level of type safety at the compile time, it's not enough when it comes to ensuring data validation at the runtime. This is where Zod comes in.

Zod provides a way to define schema validators and schema type definitions for our data. This means that we can not only validate that our data conforms to certain constraints, but also get strong typing guarantees for our data throughout our codebase. By defining our data schemas with Zod, we can ensure that our data is consistent and correctly formatted at all times.

For example, let's say we have a function that accepts a user object and returns the user's full name as a string. Without Zod, we might write something like this:

const getFullName = (user: { firstName: string; lastName: string }) =>
  `${user.firstName} ${user.lastName}`;

This function assumes that, at the runtime, the user object has firstName and lastName properties, but there's no guarantee that this is the case. With Zod, we can define a schema for our user object that ensures that these properties exist and are of the correct type:

import { z } from "zod";
 
const userSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
});
 
type User = z.infer<typeof userSchema>; // { firstName: string; lastName: string; }
 
const getFullName = (user: User) => {
  userSchema.parse(user);
 
  return `${user.firstName} ${user.lastName}`;
};

Now, our getFullName function not only validates that the user object has the correct properties, but also has strong typing guarantees for those properties. If we try to call this function with an invalid user object, Zod will throw an error, preventing runtime errors and improving the overall quality of our code.

Error handling

When it comes to data validation, error handling and feedback are crucial for providing a good user experience. Fortunately, Zod makes it easy to handle errors and provide useful feedback to your users.

When a validation error occurs with Zod, a ZodError object is thrown with details about what went wrong.

import { z, ZodError } from "zod";
 
const userSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
});
 
try {
  const invalidUser = { firstName: "John", lastName: 123 };
  userSchema.parse(invalidUser);
} catch (error) {
  if (error instanceof ZodError) {
    console.error(error.flatten());
    // {
    //   formErrors: [],
    //   fieldErrors: {
    //     lastName: ["Expected string, received number"],
    //   }
    // }
  } else {
    console.error("Unexpected error:", error);
  }
}

You can handle errors differently depending on the needs of your project. To learn more about error handling and formatting with Zod, check out the comprehensive section in the Zod documentation (opens in a new tab).

Advanced Features of Zod

While Zod is primarily known for its powerful schema declaration and validation capabilities, it also includes several advanced features that make it a versatile tool for handling data in TypeScript projects.

Union and Intersection Types

Zod supports union and intersection types to allow you to combine multiple types of data into a single schema.

Union type
import { z } from "zod";
 
const stringOrNumber = z.union([z.string(), z.number()]);
 
stringOrNumber.parse("foo"); // passes
stringOrNumber.parse(14); // passes
Intersection type
import { z } from "zod";
 
const personSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
});
 
const ROLES = ["Manager", "Developer", "Designer", "Salesperson"] as const;
const RoleEnum = z.enum(ROLES);
 
const employeeSchema = z.object({
  role: RoleEnum,
});
 
const employedPersonSchema = z.intersection(personSchema, employeeSchema);
 
employedPersonSchema.parse({
  firstName: "John",
  lastName: "Doe",
  role: "Designer",
});

Custom Types and Transformers

Zod allows you to define custom types and transformers to handle specific use cases.

import { z } from "zod";
 
const positiveIntegerSchema = z.number().refine((value) => value > 0, {
  message: "Value must be a positive integer",
});
 
const trimStringTransformer = z.string().transform((value) => value.trim());

Conditional Validation

Zod supports conditional validation to allow you to validate data based on specific conditions.

import { z } from "zod";
 
const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  state: z.string(),
  zipCode: z
    .string()
    .refine(
      (value, data) =>
        data.state === "CA"
          ? /^9[0-5][0-9]{3}$/.test(value)
          : /^[0-9]{5}$/.test(value),
      {
        message: "Invalid zip code",
      }
    ),
});

Partial Schemas

Zod supports partial schemas to allow you to define schemas with optional properties.

import { z } from "zod";
 
const optionalUserSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
  email: z.string().optional(),
});

Pick and Omit Schemas

Zod supports pick and omit schemas to allow you to select or remove specific properties from a schema.

import { z } from "zod";
 
const userSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
  email: z.string(),
});
 
const firstNameOnlySchema = userSchema.pick({ firstName: true });
const lastNameOnlySchema = userSchema.pick({ lastName: true });
const emailOmittedSchema = userSchema.omit({ email: true });

Try it!

Zod is a powerful and flexible schema declaration and validation library for TypeScript that can help you keep your data consistent and prevent errors in your code. With its intuitive API, comprehensive documentation, and advanced features, Zod provides a versatile toolset for handling data in your TypeScript projects.

Whether you're building a small personal project or a large enterprise application, Zod can help you write cleaner, more reliable code and catch errors early in the development process.

So, if you're looking for a powerful and flexible data validation library for your TypeScript projects, Zod is definitely worth checking out.