# Request Binding

> Request Binding at the Askr server boundary, with validated input and explicit failure states.

Source: [https://askrjs.com/docs/server/request-binding](https://askrjs.com/docs/server/request-binding)

Status: stable. Packages: @askrjs/askr.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
router.post('/projects', async (context) => {
  const input = await context.bind<CreateProjectInput>();
  return created(await projects.create(input));
});
```

## Bind from context

ctx.bind&lt;T&gt;() 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&amp;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.

## Documentation navigation

[Previous](https://askrjs.com/docs/server/context-and-responses/index.md) | [Next](https://askrjs.com/docs/server/middleware-and-security/index.md)
