Concept
Schema Validation vs Manual Checks
Inlining manual checks (like if (!req.body.email)) inside controllers makes code repetitive and prone to validation leaks.
Schema validation uses declarative schemas to validate inputs (body, query, params) before executing controllers:
import { z } from 'zod';
// Define expected schema
export const RegisterSchema = z.object({
body: z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(8, 'Password must be at least 8 characters'),
age: z.number().int().optional()
})
});Implementing Validation Middleware
We can write a generic middleware wrapper that validates requests against a Zod schema, returning 400 Bad Request if validation fails:
export const validate = (schema) => (req, res, next) => {
try {
// Parse input against schema
schema.parse({
body: req.body,
query: req.query,
params: req.params
});
next();
} catch (error) {
// Return structured validation errors
return res.status(400).json({
success: false,
errors: error.errors.map(err => ({
field: err.path.join('.'),
Common Mistakes
1. Validating only the request body
Security vulnerabilities can hide in query strings and route parameters (e.g. SQL Injection or ID traversal). Always validate req.query and req.params alongside req.body.
2. Failing to cast types in query string parameters
Query parameters (like /users?limit=10) arrive in Express as strings ("10"). If your schema expects a number, validation will fail unless you use preprocessing or casting functions:
// Zod parameter casting:
const QuerySchema = z.object({
query: z.object({
limit: z.string().transform(val => parseInt(val, 10)).pipe(z.number().positive())
})
});Best Practices
- Validate at the Boundary: Apply validation middleware directly on route declarations to ensure bad data is rejected before hitting controllers.
- Enforce Strict Schemas: Use
.strict()in Zod to reject requests containing unknown fields, preventing parameter injection attacks:const Schema = z.object({ username: z.string() }).strict(); - Consistent Response Layout: Maintain a standard JSON format for all validation error responses so client UI forms can map field errors automatically.
