Data validation with automatic typing using zod

and
Read on Russian

During development it is often necessary to validate some data. For example, it can be external data from client (in case we work on API), or messages from RabbitMQ.

And this is where the problem arises. What to do if we want to

1. Validate without description a mountain of different if, instanceof, typeof and other things.

2. Have TypeScript types to represent the data we want.

3. So that we don't have to maintain both types and code for validation at the same time.

To solve this problem, we can use the library zod. We will consider working with it in this article.

Installing

Before we start, let's install the library:

$ npm install zod # or via yarn $ yarn add zod

Getting Started

Let's start with a small practical example.

Suppose we have a type "User" with the following fields:

1. id of type number;

2. email of type string;

3. name of type string;

4. And githubId of type string (only for users who have linked a GitHub account, so optional).

And we want to check if the object passed to us fits this type.

To begin with, we need to describe this type using zod means:

/** * Import zod library */ import { z } from 'zod'; /** * Define our User model using zod */ const User = z.object({ id: z.string(), email: z.string(), name: z.string(), githubId: z.string().optional(), });

Now let's try to check if the object corresponds to this scheme:

const invalidUser = { id: 9, email: '[email protected]', } const parsedUser = User.parse(invalidUser)

After running this code, we will get an error because the user object is missing the name field:

Similarly, zod will report an error if the field is the wrong data type:

const invalidUser = { id: '9', email: '[email protected]', } const parsedUser = User.parse(invalidUser)

Okay, what about type generation for TypeScript? Zod can provide us with them:

Now we can use this type in our code:

Thus, by describing types with zod, we can easily validate data in randomization and use types to learn about errors at compile time.

We have looked at one of the simplest cases of using zod, but now let's look at what else the library has to offer.

Advanced validation of strings and numbers

With zod we can set rules for length, format (url, email, uuid), minimum and maximum values and other properties:

/** * Define our User model using zod */ const User = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string().nonempty(), githubId: z.string().optional(), });

Validating arrays

Let each user have an array of projects. Let's describe it:

const Project = z.object({ id: z.string().uuid(), name: z.string() }); const User = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string().nonempty(), githubId: z.string().optional(), projects: z.array(Project) });

Let's check the work:

const invalidUser = { id: '694a8b3a-d782-44fe-ad35-4a63860ab742', name: 'John', email: '[email protected]', projects: [{name: 'Zod'}] }

We see the error, everything works correctly:

The types are in place, too:

Working with objects

Zod provides many different methods to work with objects: inherit, merge, omit fields, pick fields and others.

const UserWithoutId = User.omit({id: true}) const PartialUser = User.partial(); // all fields are optional const WithImage = z.object({image: z.string()}); const UserWithImage = User.merge(WithImage);

Converting TypeScript definitions to zod schemas

With the ts-to-zod utility, we can do the opposite - generate a zod schema from type definitions in TypeScript.

Let's use this utility to convert the TypeScript interface into a zod schema:

/** * Workspace representation in DataBase */ export interface Workspace { /** * Workspace's id */ _id: string; /** * Workspace's name */ name: string; /** * Workspace's description */ description?: string; /** * Workspace's image URL */ image?: string; /** * Date when workspace was charged last time */ lastChargeDate: Date; }

Run the command to generate the scheme:

And watch the result:

// Generated by ts-to-zod import { z } from "zod"; export const workspaceSchema = z.object({ _id: z.string(), name: z.string(), description: z.string().optional(), image: z.string().optional(), lastChargeDate: z.date(), });

Now you can use the resulting schema to verify the data.

You can also find other useful zod ecosystem utilities at this link.

Conclusion

This concludes our brief overview of the zod library. We covered how this library helps with data validation and type generation so that you can be sure of your types not only at compile time but at run time as well.

See the official documentation for a full list of its features.