Using Zod for Request Validation middleware in Express.js

Middleware police ๐Ÿ˜†๐Ÿ˜†๐Ÿ˜†

ยท

2 min read

Ensuring that incoming data to an API is valid and well-formed is critical for maintaining application security and stability. Here's a guide on how to use Zod, a TypeScript-first schema declaration and validation library, to validate request data in an Express.js application. This note will help me quickly recall the implementation details.

Why Validate Request Data?

  • Security: Prevents potential security vulnerabilities.

  • Data Integrity: Ensures data conforms to expected formats and types.

  • Error Handling: Provides clear feedback for debugging and user experience.

Using Zod

Zod simplifies schema validation with a clean API and native TypeScript support, making it ideal for type-safe projects.

Middleware Implementation

This middleware validates request data against a Zod schema:

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

/**
 * Middleware to validate request data against a provided Zod schema.
 * 
 * @param schema The Zod schema to validate the request data against.
 * @returns A middleware function that parses the request body and either forwards control to the next middleware or passes an error to the error handling middleware.
 */
const validateRequest = (schema: AnyZodObject) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Validate the request body against the schema
      await schema.parseAsync({ body: req.body });
      next(); // Proceed if successful
    } catch (error) {
      // Pass validation errors to the error handling middleware
      next(error);
    }
  };
};

How It Works

  1. Schema Definition: Define a Zod schema for the request body.

  2. Middleware Function: validateRequest takes the schema and returns an async middleware function.

  3. Validation: schema.parseAsync validates req.body against the schema.

  4. Error Handling: Calls next() to proceed if valid, otherwise passes the error to the next middleware.

Example Usage

Practical example of using the middleware in an Express.js route:

import express from 'express';
import { z } from 'zod';
import validateRequest from './validateRequest';

const app = express();
app.use(express.json());

//example of schema 
const userSchema = z.object({
body:z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().positive(),
})
});

//before going to the controller its validate frist
app.post('/users', validateRequest(userSchema), (req, res) => {
  res.send('User data is valid!');
});

// Error handling middleware
app.use((err, req, res, next) => {
  if (err instanceof z.ZodError) {
    res.status(400).json({
      message: 'Validation failed',
      issues: err.errors,
    });
  } else {
    res.status(500).json({ message: 'Internal Server Error' });
  }
});

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

Conclusion

Using Zod for request validation in Express.js enhances security, ensures data integrity, and improves error handling. This approach keeps my APIs robust and my codebase maintainable, leveraging the power of TypeScript for type-safe validation.

ย