Level Up Your TypeScript Skills: Building Type-Safe Applications with Zod

A step-by-step guide on how to build type-safe applications with Zod

Introduction

TypeScript brings static typing to JavaScript, allowing developers to catch potential errors during development. However, as your codebase grows, ensuring type safety becomes increasingly challenging. Enter Zod, a TypeScript-first schema declaration and validation library that aims to make working with data structures and validation a breeze.

In this blog post, we will explore Zod in TypeScript, understand its core concepts, and walk through practical examples to demonstrate how it can be a valuable tool in your development arsenal.

Getting Started with Zod

Firstly, let’s install Zod using npm or yarn:

npm install zod
# or
yarn add zod

Requirements

  • TypeScript 4.1+!
  • You must enable strict the mode in your tsconfig.json. This is a best practice for all TypeScript projects.
// tsconfig.json 
{ 
// … 
"compilerOptions": { 
// … 
"strict": true }
 }

Now, let’s dive into the basics of Zod.

Defining Schemas with Zod

Zod allows us to define data schemas, specifying the shape and types of our data. Here’s a simple example:

// Import the Zod library for data validation
import { z } from ''zod'';

// Define a schema for user objects, ensuring data integrity
const userSchema = z.object({
  // Define properties with their expected types and validations:
  id: z.string(), // Must be a string
  username: z.string(), // Must be a string
  email: z.string().email(), // Must be a string in a valid email format
  age: z.number().int().positive(), // Must be a positive integer
});

// Create an example user object to validate
const user = {
  id: ''123'',
  username: ''john_doe'',
  email: ''john.doe@example.com'',
  age: 10,
};

// Validate the user object against the schema
const validatedUser = userSchema.parse(user);

// If validation succeeds, validatedUser will contain the parsed user object.
// If validation fails, an error will be thrown.
console.log(validatedUser); // Output the validated user object or any errors

In this example, we define a schema for a user object, specifying that id and username should be strings, email should be a valid email address, and age should be a positive integer. The parse method validates the user object against the schema.

Custom Validation and Transformation

Zod allows us to add custom validation and transformation functions. Let’s enhance our user schema with a custom validation for the username and a transformation for the age:

// Define a schema for product objects, ensuring data integrity
const productSchema = z.object({
  // Define properties with their expected types and validations:
  id: z.string(), // Must be a string
  name: z.string(), // Must be a string
  price: z.number().int().positive(), // Must be a positive integer
});

// Custom validation function for name
const isValidProductname = (name: string) => /^[a-zA-Z0-9_]+$/.test(name);

// Custom transformation function for price
const doublePrice = (price: number) => price * 2;

// Extend the product schema with custom validation and transformation
const extendedProductSchema = productSchema.extend({
  name: z.string().refine(isValidProductname, {
    message: ''Invalid Product format'',
  }),
  price: z.number().transform(doublePrice),
});

// Example user with a custom name and price transformation
const productWithCustomData = {
  id : "123",
  name: ''chair'',
  price: 15,
};

// Validate and transform the product data against the extended schema
const validatedProductWithCustomData = extendedProductSchema.parse(productWithCustomData);

console.log(validatedProductWithCustomData);
// { id: ''123'', name: ''chair'', price: 30 }

Here, we use the refine method for custom validation of the name field and the transform method to double the value of the price field.

Array Validation

Let’s consider a scenario where we have an array of user objects, and we want to ensure that each user in the array conforms to a specific schema:

// Define a schema for individual user objects
const userSchema = z.object({
  id: z.string(), 
  username: z.string(), 
  email: z.string().email(), 
  age: z.number().int().positive(),
});

// Define a schema for arrays of user objects
const arrayOfUsersSchema = z.array(userSchema);

// Example array of users to validate
const users = [
  {
    id: ''123'',
    username: ''john_doe'',
    email: ''john.doe@example.com'',
    age: 25,
  },
  {
    id: ''456'',
    username: ''the_rock'',
    email: ''the.rock@example.com'',
    age: 25,
  },
];

Here, arrayOfUsersSchema is a schema that expects an array of objects, where each object adheres to the userSchema. The parse method ensures that each element in the array satisfies the specified schema.

Advanced Schema Composition (or, and)

Zod supports advanced schema composition with union and intersection types. Let’s create schemas for both individuals and organizations:

const individualSchema = z.object({
  firstName: z.string(),
  lastName: z.string(),
});

const organizationSchema = z.object({
  organizationName: z.string(),
  registrationNumber: z.string(),
});

// Union type: person or organization
const entitySchema = individualSchema.or(organizationSchema);

// Example individual data
const individualData = {
  firstName: ''John'',
  lastName: ''Doe'',
};

const validatedIndividual = entitySchema.parse(individualData);

console.log(validatedIndividual);

// InterSection type: person and organization

const individualAndOrganizationSchema = individualSchema.and(organizationSchema);

const individualDataIntersection = {
  firstName: ''John'',
  lastName: ''Doe'',
  organizationName : ''08744'',
  registrationNumber : ''94774'',
};

const validatedIndividualInterSection = individualAndOrganizationSchema.parse(individualDataIntersection);

console.log(validatedIndividualInterSection);

In this example, we define separate schemas for individuals and organizations, then create a union type (or) and an intersection type (and) to compose more complex schemas.

Conclusion

Zod is a powerful tool for defining, validating, and transforming data structures in TypeScript. With its concise syntax and extensive features, it can enhance the robustness and maintainability of your codebase. As you explore Zod further, you’ll find it to be a valuable asset in handling data with confidence.

Remember to check the official documentation for more advanced features and options provided by Zod. Happy coding!

Continue Learning

Discover more articles on similar topics