Schema validation
Any well-structured API must validate incoming requests to ensure data integrity and application security.
Beyond that, schema validation allows the developer to write properly typed handlers — so that req.body, req.query, and req.params are strongly typed and predictable.
Dotpress uses the popular library Zod to provide expressive and robust schema validation with TypeScript-first support.
With Dotpress, you can define schemas inline when declaring a route. The framework will:
- Automatically parse and validate the request
- Reject invalid payloads with a structured 400 error
- Infer types for your handler based on your schema
- Prevent runtime surprises with fully typed data access
Basic usage
defineRoute({
method: 'post',
path: '/members',
schema: (z) => ({
body: z.object({
name: z.string(),
age: z.number().min(13),
location: z.string().optional(),
}),
}),
handler: async ({ req }) => {
// req.body is typed as { name: string; age: number; location: string | undefined }
return { received: req.body };
}
});
If the client sends malformed or missing data, the framework returns an error structured as below:
// Response Status 400 Bad Request
{
"error": "Validation failed",
"details": [
{
"source": "body",
"issues": [
{
"path": ["name"],
"message": "Required",
"code": "invalid_type"
}
]
},
{
"source": "body",
"issues": [
{
"path": ["age"],
"message": "Required",
"code": "invalid_type"
}
]
},
]
}
Supported attributes
You can define validation schemas for:
body
: JSON payloads in POST/PUTquery
: URL query strings (?sort=asc
)params
: Express route parameters (/users/:id
)
Example:
schema: (z) => ({
params: z.object({ id: z.string().uuid() }),
query: z.object({ sort: z.enum(['asc', 'desc']) }),
body: z.object({ email: z.string().email(), name: z.string() }),
})
Note: since URL params are always considered as strings, you can use the following shorthand syntax:
schema: (z) => ({
params: ['orgId', 'userId'],
})
// This will type req.params as { orgId: string; userId: string }
Failed validation
If a validation fails:
- The request is blocked before hitting your handler
- An HTTP 400 Bad Request is returned
- All relevant validation errors are included in the response
Optional Response Schema
You can also define a response
schema to validate the output of your handler:
schema: (z) => ({
response: z.object({ status: z.literal('ok'), userId: z.string().uuid() }),
})
If your handler returns an invalid object, the framework will throw a Zod validation error (which will occur a 500 Internal Error response) keeping your API consistent from end to end.
It may seem overkill in most case but can be useful in some situations:
- For internal APIs where contracts between frontend/backend must be enforced.
- For public APIs where response validation is needed to prevent missing information or exposure of personal information.
Tips
- Use Zod functions .refine() or .transform() to apply custom logic
- You can omit schema entirely for routes that don’t require validation or to prototype handlers
- You can use z.never() as a response schema to generate a 204 No Content response