How to validate request data in Express.js?
Request Validation in Express.js
Never trust client input. Validating and sanitizing request data (body, params, query) before processing it is a critical security and reliability practice.
Option 1: Zod (TypeScript-first)
bash
npm install zodjs
const { z } = require('zod');
// Define schema
const createUserSchema = z.object({
body: z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().int().min(18).max(120).optional(),
role: z.enum(['user', 'admin']).default('user'),
})
});
// Reusable validation middleware
function validate(schema) {
return (req, res, next) => {
try {
schema.parse({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (err) {
const errors = err.errors.map(e => ({
field: e.path.join('.'),
message: e.message
}));
res.status(400).json({ error: 'Validation failed', details: errors });
}
};
}
// Use in routes
app.post('/users',
validate(createUserSchema),
asyncHandler(async (req, res) => {
const user = await usersService.create(req.body);
res.status(201).json({ data: user });
})
);Option 2: express-validator
bash
npm install express-validatorjs
const { body, param, query, validationResult } = require('express-validator');
// Validation rules
const createUserRules = [
body('name').trim().isLength({ min: 2, max: 50 }).withMessage('Name must be 2-50 chars'),
body('email').isEmail().normalizeEmail().withMessage('Invalid email'),
body('age').optional().isInt({ min: 18 }).withMessage('Must be 18+'),
body('password').isStrongPassword().withMessage('Password too weak'),
];
// Collect and return errors
function checkValidation(req, res, next) {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
next();
}
app.post('/users', createUserRules, checkValidation, createUser);Validating URL Parameters & Query Strings
js
const getUserRules = [
param('id').isInt({ min: 1 }).withMessage('ID must be a positive integer'),
];
const listUsersRules = [
query('page').optional().isInt({ min: 1 }).default(1),
query('limit').optional().isInt({ min: 1, max: 100 }).default(10),
query('sort').optional().isIn(['name', 'email', 'createdAt']),
];
app.get('/users', listUsersRules, checkValidation, getUsers);
app.get('/users/:id', getUserRules, checkValidation, getUser);Option 3: Joi
bash
npm install joijs
const Joi = require('joi');
const createUserSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).required(),
role: Joi.string().valid('user', 'admin').default('user'),
});
function validate(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, { abortEarly: false });
if (error) {
const details = error.details.map(d => d.message);
return res.status(400).json({ error: 'Validation failed', details });
}
req.body = value; // use sanitized/typed value
next();
};
}Comparison
| Library | Style | TypeScript | Bundle Size |
|---|---|---|---|
| Zod | Schema-first, TypeScript-native | Excellent | Medium |
| express-validator | Chain API, Express-native | Good | Small |
| Joi | Schema-first, Hapi-origin | Good | Medium |
Summary
Always validate: body, params, and query before processing. Use a middleware pattern for clean, reusable validation. Zod is recommended for TypeScript projects for end-to-end type safety. Return 400 Bad Request with details when validation fails.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.