Appearance
Request Validation
express-zod-router uses Zod schemas to validate request data at runtime and infer TypeScript types for route handlers.
Quick example
ts
const CreateUserSchema = z.object({
name: z.string(),
email: z.string().email(),
});
api.post('/users', {
body: CreateUserSchema,
handler: async (req) => {
return createUser(req.body);
},
});Supported request inputs
| Input | Configuration | Purpose |
|---|---|---|
body | ZodType | Validates and types req.body |
params | ZodType | Validates and types req.params |
query | ZodType | Validates and types req.query |
body
Defines and validates the request body.
ts
const CreateUserSchema = z.object({
name: z.string(),
email: z.string().email(),
});
api.post('/users', {
body: CreateUserSchema,
handler: async (req) => {
return createUser(req.body);
},
});The schema provides both runtime validation and TypeScript inference.
Behavior
- The request body is validated against the Zod schema.
req.bodyis typed from the schema.- Invalid input is rejected before the handler executes.
- Validation failures use the library's standard error handling.
params
Defines and validates route parameters.
ts
api.get('/users/:id', {
params: z.object({
id: z.string().uuid(),
}),
handler: async (req) => {
return getUser(req.params.id);
},
});The resulting req.params type is inferred from the schema.
For example:
ts
req.params.id;is typed as:
ts
string;query
Defines and validates query-string parameters.
ts
api.get('/users', {
query: z.object({
page: z.coerce.number().default(1),
limit: z.coerce.number().default(20),
}),
handler: async (req) => {
return listUsers(req.query);
},
});Query-string values are received as strings by Express. Use Zod coercion when a value should be converted to another type.
For example:
ts
z.coerce.number();converts a query value such as:
text
?page=2into:
ts
2;Multiple request schemas
A route can define multiple request inputs at the same time.
ts
api.get('/users/:id', {
params: z.object({
id: z.string().uuid(),
}),
query: z.object({
includePosts: z.coerce.boolean().default(false),
}),
handler: async (req) => {
return getUser(req.params.id, {
includePosts: req.query.includePosts,
});
},
});A route can also combine request body, parameters, and query validation:
ts
api.put('/users/:id', {
params: z.object({
id: z.string().uuid(),
}),
query: z.object({
notify: z.coerce.boolean().default(false),
}),
body: z.object({
name: z.string().min(1),
email: z.string().email(),
}),
handler: async (req) => {
return updateUser(req.params.id, req.body, req.query);
},
});Typed request
The route handler receives request properties inferred from the supplied schemas.
ts
api.post('/users', {
body: CreateUserSchema,
handler: async (req) => {
req.body.name;
req.body.email;
return createUser(req.body);
},
});The same applies to route parameters and query parameters:
ts
api.get('/users/:id', {
params: UserParamsSchema,
query: UserQuerySchema,
handler: async (req) => {
req.params.id;
req.query.page;
return getUser(req.params.id);
},
});This keeps runtime validation and TypeScript types synchronized.
Validation flow
Request validation happens before the route handler executes.
text
HTTP Request
↓
Request validation
↓
Validated and typed request
↓
Route middleware
↓
Route handler
↓
Response validation
↓
HTTP ResponseIf validation fails, the handler is not executed.
Validation errors
Invalid request data produces the standard validation error response.
json
{
"error": "Validation failed",
"details": []
}See Errors for the complete error documentation.
Example
See the complete working examples:
Summary
- Use
bodyto validate request bodies. - Use
paramsto validate route parameters. - Use
queryto validate query-string parameters. - Multiple request schemas can be used on the same route.
- Zod provides runtime validation and TypeScript inference.
- Use
z.coercewhen query-string values need type conversion. - Invalid requests are rejected before the route handler executes.
- See the examples for complete working implementations.