Express Zod Safe Validation Middleware

raw JSON →
3.2.1 verified Thu Apr 23 auth: no javascript

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

error TypeError: Cannot destructure property 'params' of 'undefined' as it is undefined.
cause Attempting to use `validate({ params, query, body })` without providing one of the schema objects (e.g., `params` was not defined or passed as `undefined`).
fix
Ensure all properties (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' } ]
cause This typically occurs when `express.json()` or another body-parsing middleware is missing or placed after the `validate` middleware, leading to an undefined `req.body`.
fix
Add 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'.
cause TypeScript error when trying to infer types for `req.body`, `req.params`, or `req.query` in a route handler without using the `ValidatedRequest` utility or correctly typing the handler.
fix
Use the 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) => { ... });
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.
fix Ensure your installed `express-zod-safe` package matches the major version of your `zod` package. Upgrade `zod` to `^4.0.0` for `express-zod-safe@3.x.x` or downgrade `express-zod-safe` to `1.5.4` for `zod@3.x.x`.
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.
fix Always ensure `app.use(express.json());` (or similar body parsing middleware) is called *before* any route or middleware that uses `express-zod-safe`'s `validate` function.
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.
fix For query and path parameters that are expected to be numbers, booleans, or dates, use `z.coerce.number()`, `z.coerce.boolean()`, `z.coerce.date()`, etc., in your Zod schemas.
npm install express-zod-safe
yarn add express-zod-safe
pnpm add express-zod-safe

This quickstart demonstrates how to set up an Express route with `express-zod-safe` to validate URL parameters, query strings, and the request body using Zod schemas, including basic error handling.

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}`));