javascript
44 lines · 8 steps
A reusable Zod validation middleware for Express
A factory function turns Zod schemas into Express middleware that validates, coerces, and cleans requests before they reach your handler.
Explained by
highlit
1const { z, ZodError } = require('zod');
2
3function validate(schemas) {
4 return (req, res, next) => {
5 try {
6 if (schemas.body) req.body = schemas.body.parse(req.body);
7 if (schemas.query) req.query = schemas.query.parse(req.query);
8 if (schemas.params) req.params = schemas.params.parse(req.params);
9 next();
10 } catch (err) {
11 if (err instanceof ZodError) {
12 return res.status(422).json({
13 error: 'ValidationError',
14 details: err.issues.map((issue) => ({
15 path: issue.path.join('.'),
16 message: issue.message,
17 })),
18 });
19 }
20 next(err);
21 }
22 };
23}
24
25const createUserSchema = {
26 body: z.object({
27 email: z.string().email().toLowerCase().trim(),
28 name: z.string().min(1).max(120).trim(),
29 age: z.coerce.number().int().min(18).optional(),
30 role: z.enum(['admin', 'member']).default('member'),
31 tags: z.array(z.string()).default([]),
32 }),
33 query: z.object({
34 notify: z.coerce.boolean().default(false),
35 }),
36};
37
38router.post('/users', validate(createUserSchema), async (req, res) => {
39 const user = await UserService.create(req.body);
40 if (req.query.notify) await WelcomeMailer.enqueue(user);
41 res.status(201).json(user);
42});
43
44module.exports = { validate, createUserSchema };
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A middleware factory lets one validation function serve any route by parameterizing it with schemas.
- 2Reassigning req.body to the parse result means coerced and defaulted values flow into your handler, not just the raw input.
- 3Catching a specific error type lets you shape client-facing responses while delegating everything else to Express's error pipeline.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-reusable-zod-validation-middleware-for-express-explained-javascript-9d63/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.