Request Binding
Use request binding with the current published Askr packages.
@askrjs/askr0.0.59
How to use request binding
Start with this complete request binding shape, then replace the example data and application service with your own.
- Import the published @askrjs/askr entrypoint.
- Keep request binding configuration next to the component, route, or server composition root that owns it.
- Handle pending, unavailable, cancellation, and failure states, then verify the production path.
router.post('/projects', async (context) => {
const input = await context.bind<CreateProjectInput>();
return created(await projects.create(input));
});Bind from context
ctx.bind<T>() reads whatever supported body the request has, merges it with the query string and route params, and caches the result — call it more than once in the same handler and you get the same object back rather than a second read of the body. The standalone bind(context) function takes a plain BindContext (request, params, url, query) instead of a full ServerContext, which is what lets the same binding logic run somewhere that only has those four pieces, like a test. The generic type argument is purely a TypeScript view of the shape you expect back; bind() does not check the request against it at runtime.
Supported inputs
Three body types are understood: application/json (and any +json media type, provided the root is an object), application/x-www-form-urlencoded, and multipart/form-data, where text fields come back as strings and file fields as File objects. Repeated keys become arrays in both the query string and form bodies — /users?tag=admin&tag=editor binds tag as ["admin", "editor"] — while route parameters are always single decoded strings. GET and HEAD requests never have their body read at all, since neither method is expected to carry one.
Binding errors
A BindingError carries a fixed status of 400 and an optional field naming which part of the request caused the failure, so a caller can tell a malformed body apart from one that simply doesn't match what the handler expected. It's thrown for invalid JSON, a JSON body whose root isn't an object, malformed multipart data, and for trying to read a body that's already been consumed. Because ctx.bind() surfaces these through the normal problem+json response path rather than throwing past your handler, a try/catch is only needed if you want to customize the 400 response yourself.
Validation boundary
Binding is deliberately shallow: it won't parse dotted keys into nested objects, coerce a query string "42" into a number, or check values against a schema — the generic type parameter is a hint for your editor, not a runtime guarantee. Headers are excluded from the bound model entirely; read something like an If-Match value directly with ctx.headers.get("if-match") rather than expecting bind() to surface it. Because untrusted input passes through bind() unchanged, run it through a real validator like @askrjs/schema before relying on any of its values, especially anywhere one flows into a database query or a downstream call.