Thumbnail

Schema Validation in TypeScript with Zod

· 4 min read

The importance of schema validation

Before diving into how Zod works, it's crucial to understand why schema validation is necessary.

Schema validation ensures that data adheres to specified patterns, structures, and data types. It helps catch quality issues early in your codebase, preventing errors caused by incomplete or incorrect data types. Robust schema validation not only enhances performance but also reduces the likelihood of errors when building production-ready, large-scale applications.

What is Zod and why do we need it?

Runtime checks are crucial for ensuring validated data on the server side. When a user fills out a form, TypeScript cannot verify that the inputs meet your expectations at runtime on the server. This is where Zod comes in.

Zod helps maintain data integrity by preventing invalid data from being sent to the database. It's better to log an error on the UI, such as when a user enters numbers instead of a string, than to handle it later. Zod addresses this TypeScript limitation and ensures type safety at runtime. It allows you to create flexible schema designs and validate them against user inputs effectively.

Benefits of using Zod with TypeScript

Zod works particularly well with TypeScript. In a typical TypeScript codebase, you ensure that all the static type safety will be handled by TypeScript at compile time. Even most of the third-party packages ship their code along with their types, like how React has its types under an npm package called @types/react.

Having an additional runtime check using Zod fixes these issues with TypeScript. You might have encountered issues where you need to assign undefined or unknown to a type even after using its corresponding @types package — the sole reason being you don’t know beforehand what a user would enter or what the response structure is like.

Zod applies these runtime checks in a very concise way. That is literally like taking data types from TypeScript and extending them to Zod-specific methods. Zod automatically infers the type that you have already mentioned in TypeScript, preventing type duplication.


Primitives in Zod

import { z } from "zod"
const dataInputFromUser = z.string().min(8).max(16)
dataInputFromUser.parse("A long text")


The above code will safely parse. You can navigate the user to the next input or a page, depending on your use case.

And if we tweak the line a bit, like this:

dataInputFromUser.parse("A really long text")

It’ll throw an exception:

errors: [
    {
      code: 'too_big',
      maximum: 16,
      type: 'string',
      inclusive: true,
      message: 'String must contain at most 16 character(s)',
      path: []
    }
  ]

If you need safer exception handling, you can simply log the error using the .safeParse() method.

This is one of the simplest examples to use primitives in Zod. Primitive values are not just limited to string, but provide other methods such as number, bigint, boolean, and date. There are a couple of empty types as well, like undefined, null, and void.

Utilizing these primitives along with a few specific methods can lead to a very flexible schema design:

  • z.string().email().startsWith(string).trim().max(18).min(1)
  • z.boolean()
  • z.bigint()

The example above is chaining these primitives together to create a very functional type safety at runtime for an email input field.


Objects in Zod

Most of the user-facing form requires a couple of data inputs and validation of varying data types. This is where it’s better to use objects in Zod. You can create a schema for a set of properties you want at a runtime check:

import { z } from 'zod'

const FormData = z.object({
  firstName: z.string().min(1).max(18),
  lastName: z.string().min(1).max(18),
  phone: z.string().min(10).max(14).optional(),
  email: z.string().email(),
  url: z.string().url().optional(),
});

const validateFormData = (inputs: unknown) => {
  const isValidData = FormData.parse(inputs);
  return isValidData;
};

In the above TypeScript code, there is no way to enforce schema validation at runtime using TypeScript only, hence the inputs: unknown.

This is where z.Object() can be used for building an extensible schema validation. The data will be safely parsed if a user enters the fields that satisfy your schema definition. You’ll then be able to send that data to the server. Otherwise, an exception will be thrown.


Type inferences in Zod

Suppose you already have your type defined somewhere and you want a newly created variable to deduce its type from existing ones. In that case, Zod has a method that can infer its type

let fullName = z.string(); 
type fullName = z.infer<typeof fullName>
Abhijeet Anand

About Abhijeet Anand

Abhijeet Anand is a Backend Developer at CyberMind Works. With a knack for optimizing systems and streamlining processes, he excels in crafting robust backend solutions. Abhijeet is committed to leveraging technology to drive efficiency and innovation in every project.

Link copied
Copyright © 2024 CyberMind Works. All rights reserved.