DEV Community

Cover image for Client & server-side validation with Zod.
Rak
Rak

Posted on • Updated on

Client & server-side validation with Zod.

Validating the data we receive from various sources is crucial. It's a common challenge faced when dealing with user input, external APIs, or data read from databases.

In the JavaScript world, we have several tools to help with data validation, but one that stands out is Zod - a library for creating, manipulating, and validating schemas.

In this blog, we'll dive into the details of creating schemas using Zod. By the end, you will be able to confidently use Zod to create and validate your data models.

As a side note, I spent a good portion of my career in the fintech space with onboarding applications - aligning validation was always finicky.

Let's get started!

What is Zod?

Zod is a JavaScript library that allows developers to build schemas for their data. It is simple yet powerful, providing a fluent API for constructing schemas and validating data. With Zod, you can create complex, nested object schemas with deep type inference, default values, transformations, and more.

Setting Up

Before we start creating schemas, you'll need to install Zod using npm with the following command:

npm install zod
Enter fullscreen mode Exit fullscreen mode

Creating a Basic Schema

Let's start by creating a basic schema for a user. In this schema, a user will have a name, email, and age.

import { z } from 'zod';

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
});
Enter fullscreen mode Exit fullscreen mode

Here we define an object schema, userSchema, where name and email are string fields, with email being validated with the built-in email validator. The age is a number field with a minimum value of 0.

Validating Data with the Schema

Now, let's use our schema to validate some data.

const userData = {
  name: "John Doe",
  email: "john.doe@example.com",
  age: 30,
};

const result = userSchema.safeParse(userData);

if (!result.success) {
  console.log(result.error);
} else {
  console.log(result.data);
}
Enter fullscreen mode Exit fullscreen mode

safeParse is a method provided by Zod that returns an object. If the validation fails, success will be false, and the error object will contain information about what went wrong. If the validation passes, success will be true, and data will contain the validated data.

Advanced Schema

Zod also allows creating advanced schemas with optional fields, default values, and custom validation. Let's create an advanced user schema.

const advancedUserSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
  website: z.string().optional().url(),
  bio: z.string().default("This user hasn't written their bio yet."),
});
Enter fullscreen mode Exit fullscreen mode

In this advanced schema, the website field is optional and must be a valid URL. The bio field has a default value and will automatically be filled in if the user doesn't provide one.

Custom Validators

Zod is not limited to predefined validators; you can write your own custom validators using the .refine() method.

const passwordSchema = z.string().refine(password => password.length >= 8, {
  message: "Password must be at least 8 characters long",
  path: ['password'], // indicates this error is about the password field
});
Enter fullscreen mode Exit fullscreen mode

In this schema, the password field must be a string that is at least 8 characters long. If it's not, Zod will throw an error with our custom message.

Client & Server-Side Validation

So far, we've covered creating basic and advanced schemas using Zod, as well as using custom validators.

Client-side and server-side validation with Zod can be implemented in quite a similar way because both are run on JavaScript-based environments. The only difference would be where and when the validation is performed.

The primary advantage of using Zod in both client and server-side is the DRY (Don't Repeat Yourself) principle. You can define your validation schemas once and use them on both ends. This ensures that your data is consistent and follows the same rules across your entire application.

Client-Side Validation

On the client-side, you will typically use Zod validation in response to user interaction. For example, you might validate form data when a user submits a form. Here's an example using React:

import { z } from 'zod';
import React, { useState } from 'react';

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
});

function UserForm() {
  const [formData, setFormData] = useState({ name: '', email: '', age: '' });
  const [errors, setErrors] = useState({});

  const handleSubmit = (event) => {
    event.preventDefault();
    const result = userSchema.safeParse(formData);
    if (result.success) {
      // Proceed with form submission
      console.log(result.data);
    } else {
      // Handle validation errors
      setErrors(result.error.formErrors.fieldErrors);
    }
  };

  // The rest of your form goes here
}
Enter fullscreen mode Exit fullscreen mode

In this example, when the form is submitted, we parse the form data using the userSchema. If validation is successful, we continue with form submission; otherwise, we show the errors.

Server-Side Validation

On the server-side, you will typically use Zod validation in your route handlers, immediately after receiving a request. Here's an example using the Nitric framework:

import { api } from "@nitric/sdk";
import { z } from "zod";

const mainApi = api("main");

const userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
  age: z.number().min(0),
});

mainApi.get("/user", async (ctx) => {
  const result = userSchema.safeParse(ctx.req.json());
  if (result.success) {
    // Proceed with processing the request
    ctx.res.status = 200;
    ctx.res.json(result.data);
  } else {
    // Handle validation errors
    ctx.res.status = 400;
    ctx.res.json(result.error.formErrors.fieldErrors);
  }
  return ctx;
});
Enter fullscreen mode Exit fullscreen mode

In this example, when a POST request is made to the "/user" endpoint, we parse the request body using the userSchema. If validation is successful, we continue processing the request; otherwise, we return a 400 status code along with the validation errors.

curl -X GET -d '{"name":"John","email":"john@example.com","age":"abcd"}' -H 'Content-Type: application/json' http://localhost:4001/user

{"age":["Expected number, received string"]}%     
Enter fullscreen mode Exit fullscreen mode

Zod can be used for both client-side and server-side validation in JavaScript applications, providing a consistent validation experience across your application.

Make sure to always validate user data on the server-side, even if you have client-side validation, to ensure data integrity and security.

Top comments (0)