Validation Overview
Reference: Typebox GitHub Repository
Deserve validates request input through Typebox contracts, a zero-dependency contract library that ships with the framework. A contract describes one source of a request, the validator middleware runs it before the handler, and the handler reads typed data that already passed every rule.
This sits beside the other middleware and watches the request before it reaches a route, the same place CORS and Session hook in.
The Three Pieces
Validation comes together from three exports, each with one job:
Definebuilds a contract from a transform and optional guards. See Define Schema.Mware.validatorturns a schema into middleware that validates request sources. See Validator Middleware.Validatorreads validated data inside a handler and checks values on demand. See Reading Validated Data.

A Schema Maps Sources To Contracts
A schema is a plain object that pairs a request source with a contract:
import { Define } from '@neabyte/deserve'
// One contract per request source
const schema = {
json: Define((body: { name: string }) => body)
}Six sources exist, and each one reads from a matching part of the Context:
| Source | Reads from | Shape |
|---|---|---|
body | ctx.body() | raw parsed body |
cookies | ctx.cookie() | Record<string, string> |
headers | ctx.header() | Record<string, string> |
json | ctx.json() | parsed JSON value |
params | ctx.params() | Record<string, string> |
query | ctx.query() | Record<string, string> |
The Request Flow
A validated request moves through four steps:
- The validator middleware reads each source named in the schema.
- It runs the matching contract on that source value.
- A passing contract stores its output on request state.
- The handler reads that state with full types.

import { Define, Mware, Router } from '@neabyte/deserve'
const router = new Router({
routesDir: './routes'
})
const schema = {
json: Define((body: { name: string }) => ({ name: body.name.trim() }))
}
// Validate the JSON body before the handler
router.use('/users', Mware.validator(schema))
await router.serve(8000)export function POST(ctx: Context): Response {
// Read typed data that already passed
const { json } = Validator.read<typeof schema>(ctx)
return ctx.send.json({ created: json.name })
}Failures Become 422
A contract that rejects its input throws, and the framework maps that throw to a 422 Unprocessable Content response. The failure reasons ride on error.cause as a string array, so a custom handler reads them and surfaces exactly which field went wrong. Error shaping stays in one place through Object Details, the same router.catch that handles every other error.
A throw from client input never becomes a 500. That mapping rule lives in Reading Validated Data.
Where To Go Next
- Define Schema - write a contract with a transform and guards.
- Validator Middleware - register validation per source and per route.
- Reading Validated Data - read typed output and check params in a handler.
- Advanced Patterns - pick a schema per method on a shared prefix.