Body
Arbor does not parse the request body until an action calls getBody. This keeps requests fast when the body is not needed. When used, getBody can return structured data, raw buffers or multipart depending on the options provided.
Arbor performs normalization by default. This means that unless you opt out, Arbor:
- returns arrays when named explicitly
- otherwise returns only the first value for every field
Normalization avoids inconsistencies where a field sometimes appears as a string and sometimes as an array. If you want unmodified semantics where body is mostly untouched, use skipNormalize.
Primary Options
These options determine the fundamental parsing mode of getBody.
raw
Returns a single Buffer containing the entire request body after the request completes.
const actionUpload = createAction(async ({ getBody }) => {
const data = await getBody({ raw: true });
return { size: data.length };
});
Normalization is skipped when using raw buffers.
multipart
Parses multipart form data. Returns [body, files]:
const actionUploadAvatar = createAction(async ({ getBody }) => {
const [body, files] = await getBody({ multipart: true });
return { name: body.name, fileCount: files.length };
});
body is normalized unless you specify skipNormalize.
If there are no files or the request simply wasn't multipart files is an empty array. Each file includes headers, name, filename, contentType and data as a buffer.
raw + multipart
Combining the two returns an array of parts, each containing:
- headers
- data as
Buffer
This mode is useful for some types of low-level custom processing.
const actionParts = createAction(async ({ getBody }) => {
const parts = await getBody({ raw: true, multipart: true });
return { count: parts.length };
});
skipNormalize
Disables all normalization. Every repeated field becomes an array. This matches raw HTTP semantics but is often inconvenient.
const actionSkip = createAction(async ({ getBody }) => {
const data = await getBody({ skipNormalize: true });
return data;
});
Use this only when you need the exact low-level shape of the incoming data.
Normalization Options
These options modify the normalized body, they apply only when raw and skipNormalize are not used.
arrays
Explicitly marks fields that are returned as arrays.
const actionPets = createAction(async ({ getBody }) => {
const body = await getBody({
arrays: ["ownedPets"],
});
return body;
});
Without this option, Arbor returns the first value.
required
Marks fields that must be present. Missing fields produce a 422 error.
const actionRegister = createAction(async ({ getBody }) => {
const body = await getBody({
required: ["email", "password"],
});
return body;
});
This guarantees the key exists but does not guarantee the value is non-empty.
numbers
Converts fields into numbers. Invalid numbers produce a 422 error.
const actionAge = createAction(async ({ getBody }) => {
const body = await getBody({
numbers: ["age"],
});
return { age: body.age };
});
booleans
Converts fields into booleans. "0" and "false" become false. All other values become their truthy value.
const actionFlags = createAction(async ({ getBody }) => {
const body = await getBody({
booleans: ["active"],
});
return { active: body.active };
});
Mixing booleans and numbers
If a field is listed in both options:
- Arbor converts the value to a number.
- Then it converts the number to a boolean.
This is usually not intended. Avoid combining them.
trim
Trims whitespace from all string values. Empty strings become undefined.
const actionTrim = createAction(async ({ getBody }) => {
const body = await getBody({ trim: true });
return { name: body.name };
});
Useful for forms and user input. This will also affect fields marked required, in that empty strings would no longer be valid.
Validation
Use the validate object to enforce field-level constraints. Each key corresponds to a field name. Validators receive the normalized value.
Validators should return a short lowercase string describing the failure or undefined. Returning a string produces 422.
interface Body {
name: string;
age: number;
}
const actionValidate = createAction(async ({ getBody }) => {
const body = await getBody<Body>({
required: ["name", "age"],
numbers: ["age"],
validate: {
name: value => {
if (value.length < 3) return "too short";
},
age: value => {
if (value < 0) return "invalid";
},
},
});
return body;
});
Validation occurs after all other normalization steps.
Non Throwing Mode
throws: false allows you to receive validation or parsing errors without producing 422. Arbor returns { ok: false, errors } or { ok true, data }.
interface Body {
email: string;
name?: string;
}
const actionSafe = createAction(async ({ getBody }) => {
const result = await getBody<Body>({
required: ["email"],
trim: true,
throws: false,
});
if (!result.ok) {
return { errors: result.errors };
}
return result.body;
});
Result might look like the following when there are errors:
{
ok: false,
errors: {
email: 'is required',
},
}
It is worth noting that errors is included as ex.cause on 422 responses, and that information is sent by the default error handler. The client can study res.error.cause as it contains the same information. Useful for user-facing endpoints that need structured client errors.
Compression
Arbor supports br, gzip and deflate when the request includes a Content-Encoding header. Decompression happens automatically before body parsing.
No configuration is required.
Summary
getBody is explicit by design. Its behavior is controlled by:
- primary modes:
raw,multipart,skipNormalize - normalization tools:
arrays,required,numbers,booleans,trim - validation tools:
validate,throws
Arbor never guesses intent. You specify the shape and strictness of incoming data, and Arbor enforces it predictably.