Skip to main content

Request Body

OPRA reads, parses, and validates the request body before handing it to your handler. You declare the expected content type and data type on the operation, and call ctx.getBody() inside the handler to receive the already-validated object.


ctx.getBody<T>()

async getBody<T>(args?: { toFile?: boolean | string }): Promise<T>

Reads, parses, and validates the request body. The result is cached — calling getBody() multiple times within the same request is free.

OPRA automatically deserializes the body based on the request's Content-Type header:

Content-TypeDeserialization
application/jsonJSON.parse
application/yaml / text/yamlYAML parser
application/tomlTOML parser
multipart/*Returns MultipartReader.Item[] (see Multipart & File Uploads)

After parsing, the body is validated and coerced against the type declared in .RequestContent(). If validation fails, OPRA throws a BadRequestError before your code sees the value.

@(HttpOperation.POST()
.RequestContent(Customer)
.Response(201, { type: Customer }))
async create(ctx: HttpContext) {
const body = await ctx.getBody<Customer>();
// body is fully validated — field types coerced, unknown fields stripped
}

Declaring request body with .RequestContent()

.RequestContent() declares what content type and data type the operation accepts. It accepts either a type directly or a full options object:

// Type only — OPRA infers application/json
.RequestContent(Customer)

// Full options
.RequestContent({
type: Customer,
contentType: 'application/json',
description: 'Customer to create',
})

Options

OptionTypeDescription
typeType | stringThe data type used to validate and coerce the body.
contentTypestring | string[]MIME type(s) accepted. Defaults to application/json.
contentEncodingstringCharacter encoding, e.g. 'utf-8'.
descriptionstringHuman-readable description for the schema.
examplestringExample request body for schema documentation.
examplesRecord<string, string>Multiple named examples.

Body validation options

Body validation behavior is controlled on the request body level, not on the media type. These options can be passed to the operation decorator or to Entity templates via requestBody:

partial

By default, OPRA enforces all required fields declared on the type. Set partial to make every field optional — useful for PATCH endpoints that only carry changed fields:

@(HttpOperation.PATCH({ path: ':id' })
.PathParam('id', { type: 'integer' })
.RequestContent('application/json', { partial: true })
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const patch = await ctx.getBody<Partial<Customer>>();
// Every field is optional — only present fields are validated
}

partial: 'deep' applies the same rule recursively to nested objects.

allowNullOptionals

By default, sending null for an optional field is a validation error — the field must either be omitted or carry a valid value. When allowNullOptionals: true, clients can send null to explicitly clear a field:

@(HttpOperation.PATCH({ path: ':id' })
.PathParam('id', { type: 'integer' })
.RequestContent('application/json', {
partial: true,
allowNullOptionals: true,
})
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const patch = await ctx.getBody<Partial<Customer>>();
// patch.middleName === undefined → field was omitted, leave unchanged
// patch.middleName === null → client explicitly cleared the field
// patch.middleName === 'Jane' → client set a new value
}

allowPatchOperators

Enables _$push and _$pull array patch operator fields in the body. When active, OPRA passes these through validation alongside regular fields:

@(HttpOperation.PATCH({ path: ':id' })
.PathParam('id', { type: 'integer' })
.RequestContent('application/json', {
partial: true,
allowPatchOperators: true,
})
.Response(200, { type: Customer }))
async update(ctx: HttpContext) {
const patch = await ctx.getBody();
// patch._$push → items to append to array fields
// patch._$pull → items to remove from array fields
}

Entity.Update and Entity.UpdateMany enable all three of these options by default.

maxContentSize

Maximum body size in bytes. Requests that exceed this limit are rejected before the body is read:

@(HttpOperation.POST()
.RequestContent('application/json', { maxContentSize: 1_000_000 }) // 1 MB
.Response(201, { type: Report }))
async uploadReport(ctx: HttpContext) { ... }

Reading binary or large bodies as a file

Pass { toFile: true } to getBody() to stream the body directly to a temporary file instead of buffering it in memory. Useful for large uploads:

@(HttpOperation.POST()
.RequestContent({ contentType: 'application/octet-stream' })
.Response(201))
async upload(ctx: HttpContext) {
const file = await ctx.getBody<LocalFile>({ toFile: true });
// file.path — path to the temp file
// file.size — size in bytes
}

Pass a string path to save to a specific location instead of a temp file:

const file = await ctx.getBody({ toFile: '/uploads/incoming.bin' });

Immediate fetch with immediateFetch

By default, OPRA reads the body lazily — only when ctx.getBody() is called. Set immediateFetch: true to have the adapter read and buffer the body before the handler is called:

@(HttpOperation.POST()
.RequestContent('application/json', { immediateFetch: true })
.Response(201, { type: Customer }))
async create(ctx: HttpContext) {
// body is already in memory by the time this handler runs
const body = await ctx.getBody<Customer>(); // instant, no I/O
}

Entity.Create and Entity.Update set immediateFetch: true by default.


Methods · Entity Method Templates · Multipart