DEV Community

Cover image for Validating Request Data in Express.js using Zod and TypeScript: A Comprehensive Guide
Stephen Akugbe
Stephen Akugbe

Posted on • Edited on

Validating Request Data in Express.js using Zod and TypeScript: A Comprehensive Guide

Introduction

Express.js is a popular web framework for building Node.js applications, and validation is a crucial aspect of handling incoming data securely. In this article, we'll explore how to create a generic validation middleware using Zod, a TypeScript-first schema declaration and validation library. This middleware can be reused across different routes to ensure that incoming data adheres to predefined schemas.

Prerequisites

Before diving into the implementation, make sure you have the following prerequisites installed:

  • Node.js and npm
  • TypeScript
  • Express.js
  • Zod

Install the necessary packages by running:

npm install express body-parser zod
Enter fullscreen mode Exit fullscreen mode

Directory Structure

To keep our code organized, we'll structure our project as follows:

project-root
|-- src
|   |-- routes
|   |   |-- userRoutes.ts
|   |
|   |-- middleware
|   |   |-- validationMiddleware.ts
|   |
|   |-- schemas
|   |   |-- userSchemas.ts
|   |
|   |-- index.ts
|
|-- package.json
|-- tsconfig.json
|-- ...
Enter fullscreen mode Exit fullscreen mode

Implementing Generic Validation Middleware

Step 1: Set Up Express App

In your index.ts file, set up an Express app and configure middleware:

// src/index.ts
import express from 'express';
import bodyParser from 'body-parser';
import userRouter from './routes/userRoutes';

const app = express();
const PORT = 3000;

app.use(bodyParser.json());
app.use('/api/user', userRouter);

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Step 2: Create Zod Schemas

Define Zod schemas for different sections in your userSchemas.ts file:

// src/schemas/userSchemas.ts
import { z } from 'zod';

export const userRegistrationSchema = z.object({
  username: z.string(),
  email: z.string().email(),
  password: z.string().min(8),
});

export const userLoginSchema = z.object({
  username: z.string(),
  password: z.string().min(8),
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Build Generic Validation Middleware

Create a generic validation middleware in validationMiddleware.ts:

// src/middleware/validationMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { z, ZodError } from 'zod';

import { StatusCodes } from 'http-status-codes';

export function validateData(schema: z.ZodObject<any, any>) {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      schema.parse(req.body);
      next();
    } catch (error) {
      if (error instanceof ZodError) {
      const errorMessages = error.errors.map((issue: any) => ({
            message: `${issue.path.join('.')} is ${issue.message}`,
        }))
        res.status(StatusCodes.BAD_REQUEST).json({ error: 'Invalid data', details: errorMessages });
      } else {
        res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' });
      }
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Utilize the Generic Validation Middleware in Routes

In your userRoutes.ts, use the generic middleware for different routes:

// src/routes/userRoutes.ts
import express from 'express';
import { validateData } from '../middleware/validationMiddleware';
import { userRegistrationSchema, userLoginSchema } from '../schemas/userSchemas';

const userRouter = express.Router();

import { registerUser, loginUser } from './userController';

userRouter.post('/register', validateData(userRegistrationSchema), registerUser);
userRouter.post('/login', validateData(userLoginSchema), loginUser);

export default userRouter;
Enter fullscreen mode Exit fullscreen mode

Step 5: Implement User Controller Logic

Create a user controller in userController.ts to handle user registration and login:

// src/routes/userController.ts
import { Request, Response } from 'express';

export const registerUser = (req: Request, res: Response) => {
  // Handle user registration logic using validated data from req.body
  res.json({ message: 'User registered successfully', data: req.body });
};

export const loginUser = (req: Request, res: Response) => {
  // Handle user login logic using validated data from req.body
  res.json({ message: 'User logged in successfully', data: req.body });
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You've successfully implemented a generic validation middleware using Zod in your Express.js application. This approach allows you to create reusable validation logic for different sections of your application, promoting maintainability and scalability.

Feel free to customize the schemas and middleware according to your specific validation requirements. This structure not only enhances the readability of your code but also facilitates easier testing and code maintenance.

Top comments (9)

Collapse
 
ba2sik profile image
Ron Zano

Very nice!

Collapse
 
oppaaaii profile image
obei

amazing and simple thx

Collapse
 
hrithikraj1999 profile image
Hrithik Raj

Thanks

Collapse
 
web3vicky profile image
Vignesh Murugan

This is great, right to the point.
Thanks!!!!

Collapse
 
osalumense profile image
Stephen Akugbe

Thank you Vignesh

Collapse
 
adrielfsantos profile image
Adriel

Really useful, thanks!

Collapse
 
osalumense profile image
Stephen Akugbe

Thank you @adrielfsantos

Collapse
 
caducoder profile image
Carlos Eduardo

How can i validate the params too? Great article, thanks!

Collapse
 
andonov85 profile image
andonov85

You can try something like this for a validation.middleware.ts:

import { RequestHandler } from 'express';
import { ZodTypeAny, z } from 'zod';

const validate = (
  schema: ZodTypeAny,
  source: 'body' | 'params' | 'query'
): RequestHandler => {
  return async (req, res, next) => {
    try {
      await schema.parseAsync(req[source]);
      next();
    } catch (err) {
      if (err instanceof z.ZodError) {
        res.status(400).json({
          message: `Invalid ${source} schema`,
          errors: err.errors,
        });
      } else {
        next(err);
      }
    }
  };
};

const validateRequestBody = (schema: ZodTypeAny): RequestHandler => {
  return validate(schema, 'body');
};

const validateRequestParams = (schema: ZodTypeAny): RequestHandler => {
  return validate(schema, 'params');
};

const validateRequestQuery = (schema: ZodTypeAny): RequestHandler => {
  return validate(schema, 'query');
};

export { validateRequestBody, validateRequestParams, validateRequestQuery };

Enter fullscreen mode Exit fullscreen mode