Schema Validation with Zod and Express.js

Francisco Mendes - Dec 1 '21 - - Dev Community

Overview

In the past I've done articles on how we can use libraries like Joi and Yup to create middleware that does input validation coming from the frontend.

Although both libraries are similar, they end up having a small difference in their implementation. But if you are going to make the transition from JavaScript to TypeScript it doesn't have any problems, because the only thing you need to do is install the data type dependencies and then infer them in the code.

However most libraries are JavaScript oriented, I don't mention this point as a negative aspect, but there are libraries which are TypeScript first and very easy to use.

That's why I'm talking about Zod, if you've already tried Yup or if you already have some experience, you'll literally feel at home because the API's are similar. The only thing that changes is that Zod has many more features for TypeScript developers.

Today's example

Today I'm going to do as in other articles where we proceeded to create a middleware to perform the schema validation of a specific route. The only difference is that we are going to create an API in TypeScript.

The idea is quite simple, let's create a middleware that will receive a schema as a single argument and then validate it.

Project setup

As a first step, create a project directory and navigate into it:

mkdir zod-example
cd zod-example
Enter fullscreen mode Exit fullscreen mode

Next, initialize a TypeScript project and add the necessary dependencies:

npm init -y
npm install typescript ts-node-dev @types/node --save-dev
Enter fullscreen mode Exit fullscreen mode

Next, create a tsconfig.json file and add the following configuration to it:

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's add the following script to our package.json file.

{
  // ...
  "type": "module",
  "scripts": {
    "start": "ts-node-dev main.ts"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Now proceed with the installation of the Express and Zod dependencies (as well as their development dependencies):

npm i express zod --save
npm i @types/express --save-dev
Enter fullscreen mode Exit fullscreen mode

Let's code

And now let's create a simple API:

// @/main.ts
import express, { Request, Response } from "express";

const app = express();

app.use(express.json());

app.get("/", (req: Request, res: Response): Response => {
  return res.json({ message: "Validation with Zod 👊" });
});

const start = (): void => {
  try {
    app.listen(3333, () => {
      console.log("Server started on port 3333");
    });
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

For the API to be initialized on port 3333 just run the following command:

npm start
Enter fullscreen mode Exit fullscreen mode

Now we can start working with Zod and first let's define our schema, in this example we will only validate the response body. And let's hope the body contains two properties, the fullName and the email. This way:

// @/main.ts
import express, { Request, Response } from "express";
import { z } from "zod";

const app = express();

app.use(express.json());

const dataSchema = z.object({
  body: z.object({
    fullName: z.string({
      required_error: "Full name is required",
    }),
    email: z
      .string({
        required_error: "Email is required",
      })
      .email("Not a valid email"),
  }),
});

// ...
Enter fullscreen mode Exit fullscreen mode

Now we can create our middleware, but first we have to import NextFunction from Express and AnyZodObject from Zod. Then let's call our middleware validate and receive schema validation in the arguments. Finally, if it is properly filled in, we will go to the controller, otherwise we will send an error message to the user.

import express, { Request, Response, NextFunction } from "express";
import { z, AnyZodObject } from "zod";

// ...

const validate = (schema: AnyZodObject) =>
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      return next();
    } catch (error) {
      return res.status(400).json(error);
    }
};

// ...
Enter fullscreen mode Exit fullscreen mode

Finally, we are going to create a route with the HTTP verb of POST type, which we will use our middleware to perform the validation of the body and, if successful, we will send the data submitted by the user.

app.post("/create",
  validate(dataSchema),
  (req: Request, res: Response): Response => {
    return res.json({ ...req.body });
  }
);
Enter fullscreen mode Exit fullscreen mode

The final code of the example would be as follows:

import express, { Request, Response, NextFunction } from "express";
import { z, AnyZodObject } from "zod";

const app = express();

app.use(express.json());

const dataSchema = z.object({
  body: z.object({
    fullName: z.string({
      required_error: "Full name is required",
    }),
    email: z
      .string({
        required_error: "Email is required",
      })
      .email("Not a valid email"),
  }),
});

const validate =
  (schema: AnyZodObject) =>
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      return next();
    } catch (error) {
      return res.status(400).json(error);
    }
  };

app.get("/", (req: Request, res: Response): Response => {
  return res.json({ message: "Validation with Zod 👊" });
});

app.post("/create",
  validate(dataSchema),
  (req: Request, res: Response): Response => {
    return res.json({ ...req.body });
  }
);

const start = (): void => {
  try {
    app.listen(3333, () => {
      console.log("Server started on port 3333");
    });
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};
start();
Enter fullscreen mode Exit fullscreen mode

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 🤗

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player