Enhance Your Express Applications with Zod Library

KceeKcee
6 min read

Express.js is a great framework for building web applications. It offers flexibility, and ease of use whilst being very robust. In this article, we will use Zod to enhance the features of a web API built with Express.

Zod is a schema validation library, and it is a very good one. Schemas are structured blueprints that describe a type of data. Zod allows us to perform strict and passive validation on defined schemas, to ensure that our data conforms to the expected blueprint.

Zod can provide many useful features like data serialization and deserialization, extra static type checking for TypeScript, and can perform robust form validation, which we will cover in this article.

Form Validation

Form validation is important in API development. It improves security and prevents unexpected errors. Using Zod to perform form validation in Express is a simple process which we would go through below:

Scenario:

Imagine you have a user registration form with the following fields:

  • username: Must be a string with a minimum length of 3 characters.

  • email: Must be a valid email address.

  • password: Must be at least 8 characters long.

  • age: Must be an optional field, but if provided, it must be a positive integer greater than or equal to 18.

Instead of writing the validation rules ourselves, Zod provides a powerful and easy-to-use schema validation for the above scenario.

We’ll start with creating the project folder.

Navigate to any project directory of choice on your terminal, then run the following:

mkdir zod-app

This creates a folder called zod-app in your project directory.

Navigate to the new folder with the following:

cd zod-app

We will initialize a new Node project with the following:

npm init -y

This will create a package.json file in the zod-app folder.

Dependencies

To get started, we will install the following dependencies:

npm install typescript --save-dev
npm install zod
npm install ts-node --save-dev

With the above, we have installed TypeScript and Zod in the project.

Run the following:

npx tsc --init

This initializes a tsconfig.json file: This file is used to configure TypeScript settings.

Getting Started With Zod

We can finally get our hands dirty with some code. To get started, we will implement the scenario (the user registration form). Navigate to the project folder (zod-app) with any IDE of your choice. Then create an index.ts folder and add the following code:

import { number, object, string } from 'zod';

const UserSchema =  object({
    username: string({ required_error: 'Username is required' }).min(3),
    email: string({ required_error: 'Email is required' }).email(),
    password: string({ required_error: 'Password is required' }).min(8),
    age: number().min(18).optional(),
});

let invalidUser = {
    username: 'Joe',
    password: 'password',
    age: 17,
}

const invalidResult = UserSchema.safeParse(invalidUser);

console.log(invalidResult.success); // false
console.log(invalidResult.data) // undefined
console.log(invalidResult.error?.errors); // { errors: [ { message: 'Email is required', path: [ 'email' ] } ] }

let validUser = {
    username: 'Joe',
    email: 'joe@doe.com',
    password: 'password',
    age: 18,
}

const validResult = UserSchema.safeParse(validUser);

console.log(validResult.success); // true
console.log(validResult.data) // { username: 'Joe', ... }
console.log(validResult.error); // undefined

The UserSchema is a Zod object schema that defines the properties of the object. From the code, we see that the username email and password fields are required fields of the string type. The .email() method extends the string to restrict incoming strings to be valid email types. The .min() method extends the string to validate strings that are above the minimum length that was set. This shows how Zod provides a powerful and easy way of performing schema validation.

Applying Zod to an Express Application

We will build a middleware that uses Zod as the validator for clients’ incoming data. As shown above, Zod provides a clean and easy way to provide powerful schema validation. Let’s start by installing Express into the project:

npm install express
npm install @types/express --save-dev

Create a file named app.ts and write the following code:

import express from 'express';

const app = express();

app.use(express.json());

app.post('/users', (req, res) => {
    const user = req.body;
    console.log(user);

    res.status(201).json(user);
});


app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

This creates a simple server that has a /users endpoint. The endpoint prints the request body and returns a 201 status code. Now, we do not want to send a success status code when the data coming from the user is invalid. So we need to protect the endpoint with a middleware function.

Create a file called middleware.ts and paste in the following code:

import zod from 'zod';

export const ZodMiddleware = (schema: zod.AnyZodObject) => {
    return async (req: any, res: any, next: any) => {
        let result = schema.safeParse(req.body);

        if (!result.success) {
            res.status(400).json({ error: result.error.errors });
            return;
        }

        req.body = result.data;
        next();
    }
}

The above code creates a middleware function. It takes any Zod type (ZodAny) as a parameter and validates the incoming request body against the schema. If the result is not successful, it returns a 400 status code. If the data is valid, it calls the next function, which passes the request to the main request handler.

We will create a schema.ts file to write the code for the example schema:

import { number, object, string } from 'zod';

export const UserSchema =  object({
    username: string({ required_error: 'Username is required' }).min(3),
    email: string({ required_error: 'Email is required' }).email(),
    password: string({ required_error: 'Password is required' }).min(8),
    age: number().min(18).optional(),
});

This is the same schema as the one in the index.ts file but we have separated it for a cleaner result.

We will adjust the app.ts code to use the middleware:

import express from 'express';
import { UserSchema } from './schema';
import { ZodMiddleware } from './middleware';

const app = express();

app.use(express.json());

app.post('/users', ZodMiddleware(UserSchema), (req, res) => {
    const user = req.body;
    console.log(user);

    res.status(201).json(user);
});


app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

We have now set up the /users endpoint to use the ZodMiddleware function, which will validate the request body against the UserSchema . Now any invalid request will be blocked by the middleware. To test the API, you can use an API-testing client like Postman or the VSCode ThunderClient extension.

Conclusion

Using Zod significantly enhances and simplifies the schema validation process. Zod is a flexible, powerful and efficient library that can be used in multiple scenarios. It is also TypeScript-friendly which aids the development process.

This article has provided valuable insights into simplifying input validation in Express applications using Zod. If there are other topics you'd like to explore or if you have any questions, feel free to share them in the comments below. We’d love to hear your suggestions and know what you’d like to learn about next!

References

Zod Library Documentation: https://zod.dev/

Express Middlewares: https://expressjs.com/en/guide/using-middleware.html

0
Subscribe to my newsletter

Read articles from Kcee directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kcee
Kcee

I am a software developer with 3+ years of experience using Vue, Django and other fun frameworks. I love to experiment and test out interesting features. Writing is a hobby I enjoy and want to explore more of. I am also interested in visual arts.