Input Validation With Zod in Express: A Complete Guide
Zod is one of the best validators for Node.js. Here is how to use it in Express.
Input Validation With Zod in Express: A Complete Guide
You cannot trust the client. Validate at the boundary. Zod is one of the best validators for Node.js. Here is the complete guide.
Install
npm install zod.
Define a Schema
const { z } = require('zod');
const signupSchema = z.object({
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
email: z.string().email('Invalid email').toLowerCase().trim(),
password: z.string().min(8, 'Password must be at least 8 characters'),
age: z.number().int().min(18, 'Must be 18 or older'),
gender: z.enum(['male', 'female', 'other'])
});
Write a Validation Middleware
const validate = (schema) => (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ errors: result.error.issues });
}
req.body = result.data;
next();
};
Use It
router.post('/signup', validate(signupSchema), signup);
The signup handler only runs if the body is valid. req.body is the parsed and coerced data.
Validate Params and Query
const validateParams = (schema) => (req, res, next) => {
const result = schema.safeParse(req.params);
if (!result.success) return res.status(400).json({ errors: result.error.issues });
next();
};
const idSchema = z.object({ id: z.string().length(24) });
router.get('/users/:id', validateParams(idSchema), getUser);
Common Zod Patterns
- Email: z.string().email().
- Password: z.string().min(8).max(128).
- ObjectId: z.string().length(24) (or a regex).
- Enum: z.enum(['male', 'female', 'other']).
- Array of strings: z.array(z.string()).
- Optional: z.string().optional().
- Default: z.string().default('hey').
- Date: z.coerce.date() (parses ISO strings).
- Number from string: z.coerce.number() (parses query params).
Why Zod
- TypeScript inference (if you use TS).
- Great error messages out of the box.
- Composable schemas.
- Safe parse: returns success or error instead of throwing.
- Built-in types and transforms.
The Takeaway
Validate Express input with Zod. Define schemas, write a validate(schema) factory that uses safeParse, returns 400 with errors if invalid, replaces req.body with parsed data if valid, and calls next. Validate params and query the same way. Zod is the modern choice.
Define a Zod schema, write a validate(schema) middleware factory that uses safeParse, returns 400 with errors if invalid, replaces req.body with parsed data if valid, and calls next. Apply per route: router.post('/signup', validate(signupSchema), signup).
TypeScript inference, great error messages, composable schemas, safe parse (returns success or error instead of throwing), and built-in types and transforms. It is one of the best modern validators for Node.js.
Use a separate factory that reads req.params. Example: const idSchema = z.object({ id: z.string().length(24) }); router.get('/users/:id', validateParams(idSchema), getUser). Validate query the same way with a validateQuery factory.
Returns an object with success: true and data, or success: false and error. Unlike parse, it does not throw. This is what you want in a middleware where you want to handle errors gracefully instead of catching throws.
z.string().length(24) for a 24-char hex string (MongoDB ObjectId format). For stricter validation, use a regex: z.string().regex(/^[0-9a-fA-F]{24}$/). Zod will reject anything that does not match.
Ready to master Node.js completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master Node.js
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

