ZOD - TypeScript-first schema data validation 🤩 🔥

Zod is TypeScript-first schema validation library with static type inference from schema object. Validate at runtime and compile-time.

ZOD - TypeScript-first schema data validation 🤩 🔥
Photo by Karl Pawlowicz / Unsplash

Working with data is a regular activity during the development process. To know that the passing data meets the required shape, we can use the schema library. Their task is to parse and verify whether the data flowing in our system is correct. This can help you avoid and catch errors. The most popular are Yup and Joi. But they both have some imperfections.

And here is a library called - ZOD. Let's look at some of the advantages and examples.

Benefits

As I mentioned earlier, schema validation libraries are really useful, but ZOD wowed me with several benefits, such as:

  • Developer-friendly as possible
  • Tiny, zero-dependencies library with wide ecosystem
  • Great documentation - examples of using each method with a description
  • Methods inspired TypeScript utilities syntax
  • Supported static type inference from schema object! (This one is fabulous ️❤️‍🔥)
  • Validation in runtime and compile-time

Primivite validation

Let's start with validating the primitive values. ZOD supports all primitives with various additional specific validations:

const StringSchema = z.string().min(1, { message: "Too short" }).max(20, { message: "Too long" });
Zod simple string schema validation

With this one straight line, we check that the incoming input is a string of the correct length. Additionally, we can provide our custom error message if the schema validation fails.

ZOD allows to validate all primitives such as strings, numbers, arrays and objects of course.

Type inference

I mentioned that ZOD supports static type inference and extracts the TypeScript type from any schema.

const StringSchema = z
    .string()
    .min(1, { message: 'Too short' })
    .max(20, { message: 'Too long' }).nullable();
type StringType = z.infer<typeof StringSchema>; 
// type StringType = string | null

We can also easily infer more complex type:

const CarSchema = z.object({
    color: z.string().min(2),
    type: z.string(),
    sits: z.number().lte(5).positive(),
    haveAlarm: z.boolean()
});
type CarType = z.infer<typeof CarSchema>;
//  type CarType = {
//    color: string;
//    type: string;
//    sits: number;
//    haveAlarm: boolean;
//  }
Zod type inference from object

Thanks to ZOD, we can pass the enum to our validation schema and check the null values:

enum COLOR {
    'RED',
    'BLUE',
    'GREEN',
}

const CarSchema = z.object({
	color: z.nativeEnum(COLOR),
    sits: z.number().lte(5).positive(),
    haveAlarm: z.boolean(),
    error: z.string().nullable(),
    sport: z.boolean().optional(),
});
type CarType = z.infer<typeof CarSchema>;
// type CarType = {
//  	sport?: boolean | undefined;
//  	color: COLOR;
//  	error: string | null;
//  	sits: number;
//  	haveAlarm: boolean;
// }
Zod nullable and optional type with native Enum validation

If we want to validate an object for unrecognized keys we can use the .strict method and if there are any unknown keys in the input ZOD will throw an error.

const CarSchema = z
    .object({
        sits: z.number().lte(5).positive(),
        haveAlarm: z.boolean(),
        sport: z.boolean().optional(),
    })
    .strict();
CarSchema.parse({
    sits: -5,
    haveAlarm: true,
    sport: 'yes',
    additionalKey: 'ERROR',
});
Zod strict method used on schema
Error from ZOD strict method

Parse and SafeParse

Zod provides several methods for running schema validation. The two most common ones are .parse and .safeParse, let's take a look at the important difference between them.

const CarSchema = z.object({
    sits: z.number().lte(5).positive(),
    haveAlarm: z.boolean(),
    sport: z.boolean().optional(),
});

CarSchema.parse({
    sits: -5,
    haveAlarm: true,
    sport: 'yes',
});

When we call the parsing method on invalid data, ZOD will throw an exception with the following message:

Zod error message

The .safeParse method will also validate the data, but will not throw an exception and will not stop the application. In this case, we can handle errors and messages ourselves:

const result = CarSchema.safeParse({
    sits: -5,
    haveAlarm: true,
    sport: 'yes',
});
if (!result.success) {
   // handle error
} else {
   // handle success
}
Zod safeParse method
💡
The error message will have the same format as in the .parse method.

Error handling

Let's take a look at the error handling. ZOD provides a great API for error handling and some useful methods. The first one we can use is .format().

const result = CarSchema.safeParse({
    sits: -5,
    haveAlarm: true,
    sport: 'yes',
});
if (!result.success) {
  // handle error then return
  result.error.format();
}
Zod error format method used on ZOD error object

When we call this method on the ZOD error, we will get a friendlier format:

Zod formatted error

Another formatting method is .flatten(). The returned error will be in the following format:

Zod flattened error message

Conclusion

Thanks to ZOD, we can effectively verify the data we need. We can check a very complicated data structure, and thanks to the static TypeScript type inference, we can reduce the number of types in our system. If you want to compare ZOD and other schema validation libraries more closely, check out their great documentation.

Zod provides many useful methods for creating schemas, validation methods for primitive values, flexible error handling, and documentation with all the nuances explained.


Thanks for reading ♥️♥️

If this article was helpful, please leave a comment or 👍