Body parsing
Body parsing is lazy in Ingenium. app.use(ingenium.json()) is a no-op stub for ergonomics; the actual parse happens inside your handler the first time you call ctx.body.json(). This means handlers that don't need a body never pay the cost of buffering or parsing one.
The parser surface
ctx.body.json<T>(schema?, maxBytes?: number): Promise<T>
ctx.body.text(maxBytes?: number): Promise<string>
ctx.body.urlencoded(maxBytes?: number): Promise<Record<string, string>>
ctx.body.buffer(maxBytes?: number): Promise<Buffer>
ctx.body.stream(): Readable
ctx.body.multipart(opts?: MultipartOptions): Promise<MultipartResult>
Default maxBytes is 100,000 (matches Express's body-parser default). Override per-call. Body-too-large throws IngeniumPayloadTooLargeError mid-stream (no post-buffer rejection).
Schema validation
ctx.body.json(schema) accepts three validator shapes, detected in this order:
- Standard Schema v1 - any validator that exposes
["~standard"]. - Zod-like - anything with
safeParse(input). - Plain -
parse(input): T.
// 1. Standard Schema v1 (any validator with ["~standard"])
import { type } from 'arktype'
const User = type({ name: 'string', email: 'string' })
app.post('/users', async (ctx) => ctx.body.json(User))
// 2. Zod-like safeParse
import { z } from 'zod'
const User = z.object({ name: z.string(), email: z.string().email() })
app.post('/users', async (ctx) => ctx.body.json(User))
// 3. Plain { parse(input): T }
const User = {
parse(input: unknown): { name: string } {
if (typeof input !== 'object') throw new Error('expected object')
return input as { name: string }
},
}
app.post('/users', async (ctx) => ctx.body.json(User))
Validation failures throw IngeniumValidationError with a fields: Record<string, string> map. Standard Schema v1 issues with structured paths are dot-joined (['user', 'email'] → 'user.email').
See Schema validation for the full breakdown.
Transport-level body cap
The per-call maxBytes doesn't help if a handler reads via ctx.body.stream() and ignores it. For that, set a hard transport-level cap:
const app = ingenium({ maxRequestBytes: 2 * 1024 * 1024 }) // 2 MiB
Bodies that exceed maxRequestBytes are rejected at the transport layer before any consumer touches a byte.
Where to next?
- Schema validation - the full validator-detection rules.
- Errors -
IngeniumValidationError,IngeniumPayloadTooLargeError. - Production hardening -
maxRequestBytesand other transport-level caps. - IngeniumContext - where
ctx.bodylives.