How to validate Next.js API routes using Zod

In this article, you'll learn how to validate Next.js API routes using Zod.

Let's create a new file called /api/users.ts. The API endpoint /api/users will accept POST requests with a JSON body.

import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { method } = req;

  if (method !== "POST") {
    res.setHeader("Allow", ["GET", "POST"]);
    res.status(405).json({
      error: { message: `Method ${method} Not Allowed` },
    });
  }

  // Validate the request body

  return res.status(200).json({ message: "Success" });
}

Install Zod

Let's install Zod using npm.

npm install zod

Create the schema

Next, we will create the schema to represent the request body.

import { z } from "zod";

const schema = z.object({
  name: z.string(),
  email: z.string(),
  zipcode: z.number().min(5).max(5),
  subcribe: z.boolean().optional(),
});

The sample request body will look like this:

{
  "name": "Kiran",
  "email": "kiran@example.com",
  "zipcode": "12345",
  "subcribe": true // optional
}

Validate the request body

Next step is to validate the request body using the above schema. We will use the safeParse method to validate.

const response = schema.safeParse(req.body);

// If the request body is invalid, return a 400 error with the validation errors
if (!response.success) {
  const { errors } = response.error;

  return res.status(400).json({
    error: { message: "Invalid request", errors },
  });
}

// Now you can safely use the data to create a user
const { name, email, zipcode, subcribe } = response.data;

If the request validation fails, we will return a 400 status code with the validation errors. You can access the validation errors using the errors property on the JSON response.

Here is how the response will look like if the request body is invalid:

{
  "error": {
    "message": "Invalid request",
    "errors": [
      {
        "validation": "email",
        "code": "invalid_string",
        "message": "Invalid email",
        "path": ["email"]
      }
    ]
  }
}

You can use the library Zod Error to convert the validation errors to a more readable format.

Here is the full code for the API endpoint:

import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { method } = req;

  if (method !== "POST") {
    return res.status(405).json({
      error: { message: `Method ${method} Not Allowed` },
    });
  }

  const schema = z.object({
    name: z.string(),
    email: z.string().email(),
    zipcode: z.string().min(5).max(5),
    subcribe: z.boolean().optional(),
  });

  const response = schema.safeParse(req.body);

  if (!response.success) {
    const { errors } = response.error;

    return res.status(400).json({
      error: { message: "Invalid request", errors },
    });
  }

  const { name, email, zipcode, subcribe } = response.data;

  return res.status(200).json({ message: "Success" });
}

Here is the curl command to test the API endpoint:

curl --request POST \
  --url http://localhost:3000/api/users \
  -H 'Content-Type: application/json' \
  --data '{"name":"Kiran","email":"kiran@example.com","zipcode":"12345","subcribe":false}'