patterntypescriptexpressMajor
Request Validation Middleware with Zod
Viewed 0 times
Zodvalidationmiddlewareschema422input validation
Problem
Without input validation, APIs crash on unexpected input types, allow injection attacks, and return confusing 500 errors for bad client data.
Solution
Use Zod for schema-driven request validation as middleware.
import { z } from 'zod';
import type { RequestHandler } from 'express';
function validate<T>(schema: z.ZodSchema<T>): RequestHandler {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(422).json({
type: 'https://api.example.com/errors/validation',
title: 'Validation Failed',
status: 422,
errors: result.error.flatten().fieldErrors,
});
}
req.body = result.data; // Parsed and coerced
next();
};
}
// Usage
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
});
type CreateUserInput = z.infer<typeof CreateUserSchema>;
app.post('/users', validate(CreateUserSchema), (req, res) => {
const input: CreateUserInput = req.body; // Fully typed and validated
// ...
});Why
Zod provides runtime validation with TypeScript type inference. Defining the schema once gives both runtime safety and compile-time types without duplication.
Gotchas
- Zod strips unknown keys by default with z.object() — use .passthrough() to allow extra keys.
- Validate query params and route params separately — req.body, req.query, req.params each need their own schema.
- Zod's z.coerce.number() is needed for query params which are always strings.
Revisions (0)
No revisions yet.