Zod for Runtime Validation in Next.js
Published: 2023-09-09 15:22:54

---
title: Zod for Runtime Validation in Next.js
published: true
date: 2023-09-07 17:05:11 UTC
tags: 
canonical_url: https://chirag-gupta.hashnode.dev/zod-for-runtime-validation-in-nextjs
---

## Introduction

[Zod](https://zod.dev/) is a runtime validation library suitable for both backend and frontend. This article will guide you through utilizing Zod in Next.js for securing API routes and performing input validation.

## Zod vs. TypeScript

**Typescript's** purpose is to provide type-checking capabilities during compilation(development phase). It ensures type safety before runtime.  
**Zod's** primary focus is to catch validation errors during runtime. However, we can also infer types from Zod schemas.

## Installing Zod

```
//npm
npm install zod
//yarn
yarn add zod

```

## Creating schemas with Zod

```
const userInputSchema=z.object({
  username:z.string().min(3).max(40), //
  password:z.string().min(6).max(20)
}) // Schemas assist with runtime validation.

//Getting type from above schema
type AdminType=z.infer<typeof userInputSchema>

//Admin type will look like this
type AdminType = {
    username: string;
    password: string;
}

```

In the code snippet above, We :

- Created an object schema using `z.object()` and then we declared the username and password as strings using `z.string()` inside the schema.

- Used min and max functions, which add checks for the length of the variables.

- Used `z.infer<typeof Schema>` to get the type for the declared Schema.

### Complex example

An example containing schema for course details:

```
const courseInputSchema=z.object({
  title:z.string().min(3).max(150),
  rating:z.number().positive().min(0).max(5),
  description:z.string().min(50).max(700),
  published:z.boolean(),
  gallery:z.array(z.string().url()),//An array of valid URLs
  price:z.number().min(0).max(4999),
  imgLink:z.string()
})

type CourseType=z.infer<typeof courseInputSchema>

// this translates to
type CourseType = {
    title: string;
    rating: number;
    description: string;
    published: boolean;
    gallery: string[];
    price: number;
    imgLink: string;
}

```

## Using safeParse() to secure API routes

```
//Nextjs API route
import type { NextApiRequest, NextApiResponse } from 'next';
import dbConnect from '../../../lib/dbConnect';
import {z} from 'zod'

const userInputSchema=z.object({
  username:z.string().min(3).max(40),
  password:z.string().min(6).max(20)
})

type AdminType=z.infer<typeof userInputSchema>

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  try {
    await dbConnect(); //connecting to the db
    const parsedInput=userInputSchema.safeParse(req.body)
    if(!parsedInput.success){
      return res.status(400).json({messsage:"Incorrect input"})
    }
    //further backend logic
  } catch (error) {
    console.log(error);
  }
}

```

Let's break this down:

- We use the `safeParse()` method from Zod to validate the data from request with our declared inputSchema.

- `safeParse()` can give us these outputs:

- Before going further into the backend logic, we validate the user input using the schema and if the input is validated, we proceed. Otherwise, we send a response with status code 400 (Read about [ZodError](https://zod.dev/?id=error-handling)).

- Zod also provides us with another method `parse()`, it directly throws an error instead of returning us with the above-mentioned outputs. `safeParse()` is preferred due to its simplicity.

**Note** : We can do similar stuff in expressjs, by creating a schema and then validating request data before proceeding to further backend logic.

## Input validation on the client side

Let's define an user input schema for client-side validation:

```
//Schema
import { z } from 'zod';

const userInputSchema = z.object({
  username: z
    .string()
    .min(3, 'username must have atleast 3 characters')
    .max(40, 'username max length should be 40 characters'),
  password: z
    .string()
    .min(6, 'password must have atleast 6 characters')
    .max(20, 'password max length should be 20 characters'),
});

```

Here, the second argument to the min and max methods are custom error messages. We will use these custom messages in the next section.

### Using the above schema in a form

```
const SignupForm = () => {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] =
    useState<z.inferFlattenedErrors<typeof userInputSchema>>(); //define type for errors

  const clickHandler: React.MouseEventHandler<HTMLButtonElement> = async (
    e
  ) => {
    e.preventDefault();
    try {
      const parsedInput = userInputSchema.safeParse({ username, password });
      if (!parsedInput.success) {
        setErrors(parsedInput.error.flatten());
      }else {
        //proceed to submit the form
      }
    } catch (error) {
      setUsername('');
      setPassword('');
      console.log(error);
    }
  };

  return (
    <form className="p-4 border border-greyVariant mt-20 text-textColor w-80">
      <h2 className="text-center text-2xl">Signup</h2>
      <div className="flex flex-col mt-3">
        <label htmlFor="username">Username:</label>
        <input
          className="outline-none border-primary border-2 rounded-xl px-2 py-1"
          type="text"
          id="username"
          name="username"
          onChange={(e) => setUsername(e.target.value)}
          value={username}
        />
        {errors?.fieldErrors?.username ? (
          <p className="text-red-500">{errors.fieldErrors['username']}</p>
        ) : null}
      </div>
      <div className="flex flex-col mt-3">
        <label htmlFor="password">Password:</label>
        <input
          className="outline-none border-primary border-2 rounded-xl px-2 py-1"
          type="password"
          id="password"
          name="password"
          onChange={(e) => setPassword(e.target.value)}
          value={password}
        />
      </div>
      {errors?.fieldErrors?.password ? (
        <p className="text-red-500">{errors.fieldErrors['password']}</p>
      ) : null}
      <button
        className="mt-6 bg-primary px-3 py-2 rounded-xl text-bgColor hover:opacity-90 w-full"
        onClick={clickHandler}
      >
        Signup
      </button>
    </form>
  );
};

export default SignupForm;

```

Let's break this down :

- We declare a state variable "errors", which we will use to display our custom error messages.

- Before submitting the form, we validate our input data using `safeParse()`, and if the input data is incorrect we store the flattened error object in our state variable.

- We are displaying the error using `errors.fieldErrors["fieldName"]`, if it exists.

- This will look like:

**Additional advice** : You can also try using Zod with some form libraries like [react-hook-form](https://github.com/react-hook-form/resolvers#zod) and [conform](https://conform.guide/api/zod).

I hope you found this article helpful, see you in the next one!

Connect with me on [**Twitter**](https://twitter.com/Chirag__dev) | [**Linkedin**](https://www.linkedin.com/in/chirag-dev/) | [**Github**](https://github.com/Hardiegogo)

Happy Coding! 🎈🎈