Zod for Schema Validation: Quick Walkthrough

Empower Your TypeScript Projects with Zod: Robust Validation Made Easy!

Parvesh Saini

Parvesh Saini

·

6 min read

Introduction

Data validation is a crucial aspect of software development, ensuring that incoming data adheres to predefined rules or schemas. In the TypeScript ecosystem, Zod stands out as a powerful schema declaration and validation library. It not only enhances data validation but also strengthens the security and reliability of your applications. In this blog post, we will explore the features and advantages of using Zod for data validation.

What is ZOD?

Zod is a library that primarily focuses on schema declaration and validation in TypeScript. Zod offers a straightforward syntax to set up intricate validation rules for JavaScript objects and values.

Zod works harmoniously with TypeScript, reducing the need for repeated type declarations. You only need to declare a validator once, and it takes care of inferring the static TypeScript type.

Now, imagine a scenario where a user is filling out a form. TypeScript, on its own, can’t guarantee that the user inputs are as expected at runtime on the server side. This is where Zod steps in. It ensures data integrity and prevents the database from being populated with incorrect values. It’s always better to catch an error on the UI itself, like when a user enters numbers where a string is expected.

Zod is the perfect tool to address this issue. It ensures type safety during runtime. With Zod, you can construct a highly flexible schema design and validate it against a form or user input. It’s as simple and efficient as that!

Key Features of Zod

  • TypeScript-first Design

    Zod's design is centered around TypeScript, making it an ideal choice for developers who value type safety and efficient code. With Zod, you can easily create schemas that serve both runtime data validation and static type derivation purposes.

  • Type Inference

    The infer function in Zod empowers you to effortlessly extract types from existing schemas. This feature comes in handy when you need to establish a variable's type based on another variable's inference.

  • Runtime Validation

    Zod offers runtime validation checks, ensuring your data remains trustworthy. This is invaluable, especially in scenarios involving unpredictable user inputs or server responses.

  • Flexible Schema Design

    Furthermore, Zod grants you the freedom to craft intricate and adaptable schemas for data validation. This flexibility proves particularly advantageous when dealing with tasks like form handling and user input validation.

Primitives in Zod

Zod simplifies the validation of fundamental data types through its primitives. Primitives correspond to TypeScript's built-in types, including strings, numbers, booleans, null, and undefined. Here's how you can use Zod to validate a string's length:

import { z } from "zod";

const dataInput = z.string().min(10).max(30); //minimum length 10 characters
dataInput.parse("Say Hello to the World"); // ✅ valid string
dataInput.parse("Hello"); // ❌ error thrown

Zod's primitives enable you to enforce constraints such as string length or numeric comparisons, ensuring data integrity from the start.

Objects in Zod

Zod empowers you to validate complex data structures effortlessly. Imagine the need to validate an address object:

import * as z from 'zod';

const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  state: z.string().length(2), // State code should be 2 characters long
  zip: z.string().regex(/^\d{5}$/), // Zip code should be a 5-digit number
});

// Infer the type from the schema
type Address = z.infer<typeof addressSchema>;

function validateAddress(address: Address): void {
  try {
    // Try to parse the address using the schema
    addressSchema.parse(address);
    console.log('Address is valid');
  } catch (error) {
    // If parsing fails, log an error message
    console.error('Address is invalid:', error.message);
  }
}

// Test the function with a valid and an invalid address
const validAddress: Address = {
  street: '123 Main St',
  city: 'Sometown',
  state: 'CA',
  zip: '34578',
};

const invalidAddress: Address = {
  street: 123,
  city: '',
  state: 'California',
  zip: '1234',
};

validateAddress(validAddress); // "Address is valid" ✅
validateAddress(invalidAddress); // "Address is invalid" ❌

In this example, addressSchema is a Zod schema that defines the validation rules for a valid address object. The validateAddress function uses this schema to validate address objects at runtime. This demonstrates the flexibility of Zod, as it allows you to define and validate intricate data structures with ease. It’s a testament to the power and versatility of Zod in handling complex validation tasks.

Custom Validations with Zod's Refinements

Zod’s refinements are a powerful feature that allows you to add custom validation logic to your schemas. This means you can go beyond the built-in validation rules and create rules that are specific to your application’s needs.

const positiveNumberSchema = z.number().refine((n) => n > 0,
    { message: "Number must be positive" });

const result = positiveNumberSchema.safeParse(-5);

if (!result.success) {
  console.log(result.error); // "Number must be positive"
}

The refine method is used to add a custom validation rule. The first argument to refine is a function that takes a value and returns true if the value passes the validation rule and false otherwise. In this case, the validation rule is that the number must be positive. The second argument to refine is an options object where you can specify a custom error message.

Type Inferences in Zod

With Zod, you only need to define your validation rules once, and Zod takes care of figuring out the specific TypeScript type automatically. This makes it simple to combine basic types to create more complex data structures. Zod also lets you easily create new types based on existing ones. This makes your coding process smoother, ensuring that your code is correct and dependable.

For instance, you can create objects with types inferred from schema definition:

import * as z from 'zod';

// Define the schema for a product
const productSchema = z.object({
  id: z.number(),
  name: z.string(),
  description: z.string(),
  price: z.number().refine(price => price > 0, {
    message: 'Price must be a positive number',
  }),
  inStock: z.boolean(),
});

// Infer the type from the schema
type Product = z.infer<typeof productSchema>;

// Product type will look something like this:
/* type Product = {
  id: number;
  name: string;
  description: string;
  price: number;
  inStock: boolean;
}; */

In this example, productSchema is a Zod schema that defines the validation rules for a valid product object. The Product type is then inferred from this schema using Zod’s infer function. Now, you can use the Product type in your TypeScript code to ensure type safety.

It’s particularly useful in large codebases where maintaining consistency in data structures is crucial. It also helps in catching potential type-related bugs at compile time, thereby making the debugging process easier.

Conclusion

To sum it up, Zod is like a helpful friend when you’re working on TypeScript projects. It helps make sure your code is safe and dependable. It checks your code both during and after writing it, allows you to easily design how your data should look, and helps you manage complicated data without any hassle. With Zod, you can make sure your data is correct, avoid potential issues, make error handling easier, and overall, make your applications stronger and easier for developers to work with.

Well, that's enough of 'ZOD' for the day. Thank you so much for tuning in, I'll see you in the next one. 👋



Join the newsletter

Get the latest updates straight to your inbox.

Your privacy is important. I never share your email.

© 2024 Parvesh Saini. All rights reserved.