KosmoJS extends the standard Hono/H3/Koa context with three additions: unified bodyparser and metaparser APIs, and ctx.validated for type-safe access to validated request data.
Unified Bodyparser
ctx.bodyparser works the same regardless of framework:
await ctx.bodyparser.json() // JSON request body
await ctx.bodyparser.form() // URL-encoded or multipart form
await ctx.bodyparser.raw() // raw body bufferResults are cached - calling the same parser multiple times doesn't re-parse the request.
In practice you rarely call this directly. Define a validation schema in your handler and the appropriate parser runs automatically, placing the result in ctx.validated.
Unified Metaparser
ctx.metaparser does the same for request metadata:
ctx.metaparser.params() // route params, normalized
ctx.metaparser.query() // query parameters, normalized
ctx.metaparser.headers() // headers, as a plain object
ctx.metaparser.cookies() // parsed cookiesThese are synchronous - there is nothing to await - and cached the same way.
params() and query() hand back normalized values rather than raw strings: splat params are split into arrays, and values are coerced to match your declared types - numbers for params, numbers and booleans for query. headers() and cookies() are plain parses.
Both ctx.metaparser and ctx.bodyparser are rarely used in handlers directly. They are most useful in edge middleware and in a custom validator. where ctx.validated.* is not filled yet.
They are not there in api/app.ts, which runs before the context is extended.
Validated Data Access
ctx.validated holds the validated, typed result for each target you defined:
export default defineRoute<"users">(({ POST }) => [
POST<{
json: Payload<CreateUser>,
query: { limit: number },
headers: { "x-api-key": string },
}>(async (ctx) => {
const user = ctx.validated.json; // validated JSON body
const limit = ctx.validated.query; // validated query params
const apiKey = ctx.validated.headers; // validated headers
}),
]);Route Parameters
Validated params are available at ctx.validated.params, typed according to your refinements:
export default defineRoute<"users/[id]", [number]>(({ GET, POST }) => [
GET<{
query: { page: string; filter?: string },
}>(async (ctx) => {
const { id } = ctx.validated.params; // number
const { page, filter } = ctx.validated.query;
}),
POST<{
json: Payload<User>,
}>(async (ctx) => {
const { id } = ctx.validated.params; // number
const user = ctx.validated.json;
}),
]);ctx.metaparser.params() gives you the same normalized values before validation runs - useful in edge middleware, where ctx.validated is still empty.
The underlying raw params still exist if you need them:
- Hono -
ctx.req.param() - H3 -
event.context.params - Koa -
ctx.params