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.
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`.
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.
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.