Schema Validation with Zod

Last updated on February 20th, 2024 at 02:26 pm

Languages, frameworks, tools, and trends

Schema Validation with Zod in 2023

By September 14, 2023 10 min read

What is Zod validation all about? We’ve got you covered. “Never trust user input”. That may sound extreme, but it is an important security principle in programming. In today’s world, where accurate data is important, it is imperative to ensure that the data we receive or store in our applications is valid and conforms to a certain schema or structure.

To achieve this, developers often use schema validation tools and libraries that help them validate incoming data against a predefined schema. Zod, a popular JavaScript validation library, has emerged as a powerful tool for schema validation, enabling developers to validate and sanitize data in their applications.

This blog post will explain the concept of schema validation, explore the features of Zod, and demonstrate Zod validation, and its usage to ensure robust data integrity in JavaScript applications.

Understanding schema validation

Schema validation is the process of verifying that incoming data conforms to a set of predefined rules or specifications, known as a schema. Schema validation is used to ensure the quality of data, to prevent errors, and to improve the performance of applications. Schema validation is particularly crucial when dealing with user inputs, API responses, or any external data source.

Zod validation: Criteria for validation schemas

Criteria for validation schemas

Validation schemas typically encompass a range of criteria, including:

  • Data types: Specifying the expected type of data, such as strings, numbers, booleans, arrays, objects, etc.
  • Format constraints: Defining rules for the format of data, such as valid email addresses, phone numbers, dates, and more.
  • Structure: Ensuring the correct structure of nested objects, arrays, and their respective properties.
  • Validation conditions: Specifying conditions under which data is considered valid or invalid.

Proper schema validation can help to prevent errors, improve performance, and ensure data security.

What is Zod?

Zod library, according to its documentation, is a TypeScript-first schema declaration and validation library. “schema” in this context refers to any data type, from a simple string to a complex nested object. It provides a simple, declarative syntax for defining complex validation rules for objects and values in JavaScript.

Zod provides a declarative way to define and validate data schemas using a clean and intuitive syntax. Zod is heavily influenced by TypeScript’s type system, which makes it particularly appealing to TypeScript developers.

Zod is designed to be as developer-friendly as possible. Zod integrates well with Typescript, helping to eliminate duplicate type declarations. With Zod, you declare a validator once and Zod will automatically infer the static TypeScript type.

Zod validation: Key features of Zod

Key features of Zod

Some key features of Zod include:

  • Static type inference: Zod can automatically infer the TypeScript type of a data structure based on its schema. This improves the readability and maintainability of your code.
  • Flexible: Zod supports a wide variety of data types and validation rules. It can thus be used to validate a wide variety of data structures.
  • Expressive schema definition: Zod allows you to define schemas in a concise and expressive way. This makes it easy to define complex schemas and to reuse schemas across your code.
  • Custom validation rules: Zod allows you to define custom validation rules. This gives you the flexibility to validate data in a way that is specific to your needs.
  • Error messages: Zod allows you to provide custom error messages for different validation errors. This feature enhances the user experience by providing clear and contextual error messages that guide users toward providing valid data.
  • Composable schemas: Zod schemas can be composed to create more complex schemas. This makes it easy to reuse schemas and to create schemas that are tailored to your specific needs.

Why do we need Zod

Some developers might reason, Why we need Zod when we are already using Typescript. Well, Typescript helps with static type checking, but it only does this at compile time. After the build process, the type safety of Typescript disappears.

Zod library solves this problem. It helps with type checking and safety during runtime, thus helping us to ensure increased security and reliability for our applications. With Zod, you can create a schema and use it to verify form inputs and user input at runtime.

Zod library can be used with Javascript applications, but its full benefits come to the fore when used with Typescript. Zod can automatically generate TypeScript types from your Zod schema, keeping the Zod validation schema and TypeScript static types in sync.

Installation 

To use Zod, the following are required:

  • Node.js
  • Typescript 4.5+

Zod can be installed using npm or yarn.

“`bash

# using npm

npm install zod

# using yarn

yarn add zod

“`

Zod primitives 

Zod primitives are the basic building blocks of Zod validation schemas. They represent the different types of data that can be validated.

The following are examples of the primitives in Zod:

“`typescript

import { z } from “zod”;


// primitive values

z.string();

z.number();

z.bigint();

z.boolean();

z.date();

z.symbol();


// empty types

z.undefined();

z.null();

z.void(); // accepts undefined


// catch-all types

// allows any value

z.any();

z.unknown();


// never type

// allows no values

z.never();

“`

Let’s make use of a primitive to create a basic schema validation.

“`typescript
import { z } from “zod”;

// creating a schema for strings
const stringSchema = z.string();

// parsing
stringSchema.parse(“simple string”);
“`
The above code creates a schema for verifying a string.
“`typescript
stringSchema.parse(12); // => throws ZodError
// ZodError: [
// {
// “code”: “invalid_type”,
// “expected”: “string”,
// “received”: “number”,
// “path”: [],
// “message”: “Expected string, received number”
// }
// ]
“`

When a different data type is passed in, it throws an exception.

“`typescript

// “safe” parsing (doesn’t throw error if validation fails)

stringSchema.safeParse(“simple string”); // => { success: true; data: “simple string” }

stringSchema.safeParse(12); // => { success: false; error: ZodError }

“`

At times, we do not want our application to throw an error when Zod validation encounters a different data type from what is required. To prevent throwing an error, you can use the safeParse() method which returns an object with a boolean property called success which shows if the validation failed or not.

Zod objects 

Zod allows us to combine primitives to create flexible Zod schemas for validating objects. In the example below, we are going to have a model “User” with the following fields:

  1. name of type string
  2. email of type string
  3. age of type number, which is optional.

All fields are required by default, so to make a field optional, we need to explicitly state it in the schema creation.

“`typescript

import { z } from “zod”;


const User = z.object({

  name: z.string(),

  email: z.string().email(),

  age: z.number().optional()

});

“`

Now using the above Zod validator schema, let us try validating an input.

“`typescript

const invalidUser = {

  name: “John Doe”,

  age: “24”,

};


const parsedUser = User.parse(invalidUser);

// ZodError: [

//     {

//       “code”: “invalid_type”,

//       “expected”: “string”,

//       “received”: “undefined”,

//       “path”: [

//         “email”

//       ],

//       “message”: “Required”

//     },

//     {

//       “code”: “invalid_type”,

//       “expected”: “number”,

//       “received”: “string”,

//       “path”: [

//         “age”

//       ],

//       “message”: “Expected number, received string”

//     }

//   ]

“`

After running the code, we get a ZodError because we failed to pass in the email field which was required. We also get an error for the age field, since a string data type was passed in instead of a number.

The Zod object has a number of methods that make working with Zod schemas easier. Some of the most commonly used methods are:

  • shape: This method is used to access the schemas for a particular key.
  • keyof: This method is used to create an enum from the keys of a Zod object schema.
  • extend: This method is used to extend a Zod object schema by adding new properties or overriding existing properties.
  • merge: This method is used to merge two object schemas into a single Zod schema.
  • pick: This method is used to create a new Zod object schema that only includes a subset of the properties from the original schema.
  • omit: This method is used to create a new Zod object schema that excludes a subset of the properties from the original schema.
  • partial: This method is used to create a new Zod object schema where all or some properties of the original schema are optional. A partial schema is a schema that only requires some of the properties from the original schema.
  • deepPartial: The partial method is only one level deep so the deep partial method is used to extend into nested properties and marks them as optional.
  • required: This method is used to create a new Zod object schema where all or some properties of the original schema are required.

“`typescript

import { z } from “zod”;


const LoginSchema = z.object({

  email: z.string().email(),

  password: z.string(),

});


LoginSchema.shape.email; // string schema


const keySchema = LoginSchema.keyof(); // [“email”, “password”]


const SignupSchema = LoginSchema.extend({

  confirmPassword: z.string(),

});

type SignupType = z.infer<typeof SignupSchema>;

// {

//   email: string;

//   password: string;

//   confirmPassword: string;

// }


const UserSchema = z.object({

  name: z.string(),

  email: z.string().email(),

  age: z.number().optional(),

});


const RegisterUserSchema = SignupSchema.merge(UserSchema);

type RegisterUserType = z.infer<typeof RegisterUserSchema>;

// {

//   email: string;

//   password: string;

//   confirmPassword: string;

//   name: string;

//   age?: number | undefined;

// }“`

Custom error messages 

Zod allows you to define custom error messages. This gives you the flexibility to tailor the error messages to your specific needs.

These error messages can be customized when creating a Zod schema:

“`typescript

const name = z.string({

  required_error: “Name is required”,

  invalid_type_error: “Name must be a string”,

});


const userSchema = z.object({

  username: z

    .string()

    .min(4, { message: “Username must be at least 4 characters long” }),

  email: z.string().email({ message: “Invalid email address” }),

  age: z.number().gte(18, { message: “You must be at least 18 years old” }),

});

“`

Zod refinements

Zod mirrors Typescript’s type system as closely as possible, but there are certain validation types that cannot be represented using Typescript’s type system. For this reason, Zod provides a refinements API that can be used to provide additional custom validation functions.

Zod refinements are defined using the refine method, which takes two arguments:

  • A function that performs the validation logic.
  • An optional object OR a function that returns an object that specifies the configuration options for the refinement.

The function passed to the refine method takes one input which is the inferred type of the schema and returns a truthy or falsy value. The function should not throw but instead, it should return a falsy value to signal failure.

The configuration object for the refinement can be used to specify the following options:

  • message: The error message to be displayed if the value is invalid.
  • path: The property of the schema that generates the failure.

“`typescript

const username = z.string().refine((i) => i.length <= 25, {

  message: “Your username cannot be more than 25 characters”,

});

“`

In this example, the refine method is used to validate the length of the string. The function passed to the refine method checks the length of the string and returns true if the length is less than or equal to 25. If the length is greater than 25, the function returns false.

Zod refinements can be asynchronous. When using an asynchronous function for refinement, the parseAsync or safeParseAsync method should be used to parse and validate the data.

“`typescript

const stringSchema = z.string().refine(async (val) => val.length <= 8);


await stringSchema.parseAsync(“hello”); // => returns “hello”

“`

Zod also provides a second refinement method called superRefine. The superRefine method allows the creation of multiple issues for a single invalid value. It also allows for the customization of the error codes for the issues created. If ctx.addIssue is not called during the execution of the function, validation passes.

“`typescript

const Strings = z.array(z.string()).superRefine((val, ctx) => {

  // first issue

  if (val.length > 3) {

    ctx.addIssue({

      // custom error code

      code: z.ZodIssueCode.too_big,

      maximum: 3,

      type: “array”,

      inclusive: true,

      message: “Too many items 😡”,

    });

  }


  // second issue

  if (val.length !== new Set(val).size) {

    ctx.addIssue({

      code: z.ZodIssueCode.custom,

      message: `No duplicates allowed.`,

    });

  }

});


Strings.parse([“a”, “b”, “c”, “c”, “d”])

// ZodError: [

//   {

//     “code”: “too_big”,

//     “maximum”: 3,

//     “type”: “array”,

//     “inclusive”: true,

//     “message”: “Too many items 😡”,

//     “path”: []

//   },

//   {

//     “code”: “custom”,

//     “message”: “No duplicates allowed.”,

//     “path”: []

//   }

// ]

“`

In this example, the superRefine method is used to validate the length of an array of distinct strings. The function first checks if the length of the array is greater than 3 and then adds an error message to be returned during validation along with a customized error code. The function also checks for duplicates and adds a second error message.

Zod custom validation 

Zod allows us to provide custom validation logic via refinements. An example of where this might be needed is checking whether a password field and the corresponding confirm password field are the same.

“`typescript

import { z } from “zod”;


const passwordForm = z

  .object({

    password: z.string(),

    confirmPassword: z.string(),

  })

  .refine((data) => data.password === data.confirmPassword, {

    message: “Passwords do not match”,

    path: [“confirmPassword”], // path of error

  });


passwordForm.parse({ password: “asdf”, confirmPassword: “qwer” });

“`

The above code would result in the error below, showing which path in the object resulted in the error.

“`typescript

// ZodError: [

//     {

//       “code”: “custom”,

//       “message”: “Passwords do not match”,

//       “path”: [

//         “confirmPassword”

//       ]

//     }

//   ]

“`

Type Inference

One of the beauties of Zod is that it integrates well with Typescript and its type-checking system. With Zod, you declare your Zod validator once and the static types would be inferred automatically by Zod.

“`typescript

import { z } from “zod”;


const User = z.object({

  name: z.string(),

  email: z.string().email(),

  age: z.number().optional(),

});


type UserType = z.infer<typeof User>;

//  {

//     age?: number | undefined;

//     name: string;

//     email: string;

// }

“`

The resulting static type generated can be used to type a function parameter as needed.

“`typescript

const validateUser = (user: UserType) => {

  const validUser = User.parse(user);


  return validUser;

};

“`

Conclusion

Schema validation is a critical part of any software development process, ensuring data integrity and consistency. Zod provides a simple and powerful solution for schema validation in TypeScript projects. With its intuitive syntax, built-in validation rules, and support for custom validation, Zod makes it easy to define and validate data structures. By using Zod for schema validation, developers can reduce errors, improve code quality, and build more robust applications.


FAQs

  1. What is Zod used for?

    Zod is primarily used for data validation in TypeScript applications. It enables developers to define and enforce precise data structures, ensuring that incoming data conforms to expected types and shapes. This is particularly valuable in web development for verifying user input, API responses, or configuration data.
  2. What type of schema is Zod validation?

    Zod validation is a runtime validation library for TypeScript. It provides a way to define and enforce data structures and types during runtime, making it a runtime type schema.
  3. What is the bundle size of Zod?

    The bundle size of Zod varies depending on usage, but it’s generally lightweight, making it suitable for front-end applications where minimizing bundle size is crucial.
  4. What is Zod for frontend validation?

    Zod is designed for frontend validation in web applications. It enables you to specify and validate data shapes and types, ensuring data consistency and preventing errors in user inputs.
  5. How to use Zod with validator.js?

    To use Zod with validator.js, you can create Zod schemas for your data and then employ validator.js functions to validate data against those schemas. This combination empowers you to perform both static and runtime validation for enhanced data integrity in your frontend applications.

Join a network of the world's best developers and get long-term remote software jobs with better compensation and career growth.

Apply for Jobs

Summary
Ensuring Data Integrity through Zod Validation
Article Name
Ensuring Data Integrity through Zod Validation
Description
This blog post introduces the Zod library and discusses how Zod validation can maintain data integrity in apps and prevent incorrect data from entering the app.

Author

  • Anderson Osayerie

    Anderson Osayerie is an experienced Web Developer and Technical Writer with a proven track record of crafting and creating products for global companies. He is very interested in creating aesthetically pleasing and functional web applications.

Comments

Your email address will not be published