Express Zod Safe Validation Middleware
raw JSON →Express Zod Safe is a TypeScript-first middleware for Express.js applications, offering robust and typesafe validation of incoming HTTP request bodies, URL parameters, and query strings. It leverages Zod schemas to define validation rules, ensuring data integrity and preventing invalid or malicious data from reaching application logic. The current stable version is 3.2.1, which targets Zod v4.0.0 and above. While a specific release cadence isn't published, it appears to follow Zod's major version updates. Key differentiators include its strict type safety, seamless integration with the Express middleware stack, comprehensive coverage of request parts (body, params, query), and developer-friendly error handling, aiming to be a more robust alternative to similar packages like `zod-express-middleware`.
Common errors
error TypeError: Cannot destructure property 'params' of 'undefined' as it is undefined. ↓
params, query, body) passed to validate are defined Zod schemas or empty objects (z.object({})) if not needed, instead of being undefined. error ZodError: [ { code: 'invalid_type', expected: 'object', received: 'undefined', path: [], message: 'Expected object, received undefined' } ] ↓
app.use(express.json()); (or express.urlencoded()) early in your middleware chain, ensuring it runs *before* any route handlers or middleware that use express-zod-safe. error Argument of type 'Request<Params, ResBody, ReqBody, ReqQuery>' is not assignable to parameter of type 'object'. ↓
ValidatedRequest type from express-zod-safe to correctly type your Express Request object. For example: app.post('/user', validate({ body: userSchema }), (req: ValidatedRequest<typeof userSchema>, res) => { ... }); Warnings
breaking Express Zod Safe versions are tied to Zod major versions. For `express-zod-safe@3.x.x`, Zod v4.0.0 or higher is required. Older versions like `express-zod-safe@1.5.4` are compatible with Zod v3.x.x. Using incompatible versions will lead to runtime errors or incorrect validation behavior. ↓
gotcha The `validate` middleware *must* be placed after any other middleware that parses or modifies the request body, such as `express.json()` or `express.urlencoded()`. If `validate` is used before a body parser, `req.body` will be undefined, and body validation will fail or report an empty object. ↓
gotcha All query parameters and URL parameters are strings by default in Express. When using Zod schemas for these, use `z.coerce.<type>()` (e.g., `z.coerce.number()`, `z.coerce.boolean()`) if you expect non-string types. Failing to coerce will result in validation errors for non-string input that looks like numbers or booleans. ↓
Install
npm install express-zod-safe yarn add express-zod-safe pnpm add express-zod-safe Imports
- validate wrong
import { validate } from 'express-zod-safe';correctimport validate from 'express-zod-safe'; - ValidatedRequest wrong
import { ValidatedRequest } from 'express-zod-safe';correctimport type { ValidatedRequest } from 'express-zod-safe'; - z wrong
import z from 'zod';correctimport { z } from 'zod';
Quickstart
import express from 'express';
import validate from 'express-zod-safe';
import { z } from 'zod';
const app = express();
app.use(express.json()); // Essential for parsing JSON bodies
// Define your Zod schemas for request parts
const userParamsSchema = z.object({
userId: z.string().uuid(),
});
const userQuerySchema = z.object({
age: z.coerce.number().optional().default(18), // Coerce string to number, provide default
country: z.string().min(2).max(2).optional()
});
const userBodySchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email address'),
isActive: z.boolean().default(true)
});
// Use the validate middleware in your route definition
app.post('/user/:userId', validate({
params: userParamsSchema,
query: userQuerySchema,
body: userBodySchema
}), (req, res) => {
// Access validated data directly from req.body, req.params, req.query
console.log('Validated user ID:', req.params.userId);
console.log('Validated age:', req.query.age);
console.log('Validated body:', req.body);
res.status(200).json({ message: 'User data is valid!', data: { userId: req.params.userId, age: req.query.age, ...req.body } });
});
// Basic error handling middleware for Zod errors
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
if (err instanceof z.ZodError) {
return res.status(400).json({ errors: err.issues });
}
console.error(err);
res.status(500).send('Something broke!');
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));