Creating Custom Express Middleware for Request Validation using Zod

Mastering robust input validation and type safety in Node.js and Express by building custom middleware powered by Zod schemas and TypeScript.

In any production-grade backend application, incoming HTTP requests from untrusted clients represent a major security and data integrity vector. Accepting malformed payloads, missing required fields, or incorrectly typed query parameters can lead to uncaught runtime exceptions, database corruption, or security vulnerabilities such as injection attacks and mass assignment.

While traditional Express applications often rely on imperative validation libraries like `express-validator` or manual `if/else` checks, modern TypeScript backend development thrives on declarative schema validation. **Zod** is a TypeScript-first schema declaration and validation library that allows developers to define types once, validate runtime data seamlessly, and automatically infer TypeScript types.

This comprehensive guide explores how to design and implement a generic, highly reusable Express request validation middleware using Zod to validate request bodies, query parameters, and URL route parameters.

The Benefits of Schema-Driven Validation

Zod stands out in the Node.js ecosystem because it eliminates the disconnect between compile-time TypeScript types and runtime validation logic.

• Single Source of Truth: By defining a Zod schema, you simultaneously generate runtime validation parsers and static TypeScript types using `z.infer`.

• Detailed Error Messaging: When validation fails, Zod returns granular error objects specifying exact field paths, failed constraints, and custom error messages.

• Automatic Type Coercion and Sanitization: Zod allows you to transform, sanitize, and coerce incoming string payloads into correct data types (such as numbers or dates) before they reach your controller logic.

Setting Up Your Project Environment

To get started with Zod validation in an Express TypeScript project, install `zod`, `express`, and necessary type definitions.

BASH
Installing Express and Zod packages.
npm install express zod
npm install --save-dev typescript @types/express ts-node

Creating a Reusable Zod Validation Middleware

Instead of writing bespoke validation logic for every individual route, we can build a generic higher-order middleware function that accepts a Zod object schema and validates `req.body`, `req.query`, or `req.params`.

TypeScript
Generic Express validation middleware using Zod.
import { Request, Response, NextFunction } from 'express';
import { AnyZodObject, ZodError } from 'zod';

export function validateRequest(schema: AnyZodObject) {
  return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
    try {
      // Validate request body, query parameters, and route parameters collectively
      await schema.parseAsync({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        res.status(400).json({
          success: false,
          message: 'Validation failed',
          errors: error.errors.map((err) => ({
            path: err.path.join('.'),
            message: err.message,
          })),
        });
        return;
      }
      res.status(500).json({ success: false, message: 'Internal Server Error during validation' });
    }
  };
}

Applying Validation to Express Routes

With the generic middleware created, you can define strict validation schemas for user registration or resource creation endpoints.

TypeScript
Defining user registration schema and attaching validation middleware.
import express from 'express';
import { z } from 'zod';
import { validateRequest } from './validateMiddleware';

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

const createUserSchema = z.object({
  body: z.object({
    email: z.string().email('Invalid email address format'),
    password: z.string().min(8, 'Password must be at least 8 characters long'),
    age: z.number().int().positive().optional(),
  }),
});

app.post('/api/users', validateRequest(createUserSchema), (req, res) => {
  // At this point, req.body is fully validated and typed
  const { email, password, age } = req.body;
  
  res.status(201).json({
    success: true,
    message: 'User created successfully',
    data: { email, age },
  });
});

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

Summary

Creating custom Express middleware for request validation using Zod elevates backend code quality by combining runtime input safety with static TypeScript type inference.

By centralizing validation rules into clean Zod schemas and processing them via reusable middleware, engineering teams can prevent invalid data from penetrating core business logic, resulting in more secure, resilient, and maintainable Node.js applications.