Request Bundle
Every action in Arbor receives a single argument: the request bundle. It is an immutable object that collects everything your action needs to handle a request.
interface Bundle {
req: IncomingMessage;
res: ServerResponse;
url: URL;
context: Record<string, unknown>;
params: Record<string, string>;
methods: string[];
cookies: Cookies;
getBody: GetBody;
}
The router builds this bundle once per request and freezes it before passing it through your actions.
req and res
req and res are the standard Node HTTP objects.
const actionLog = createAction(({ req, res }) => {
console.log(req.method, req.url);
res.setHeader("X-Request-Id", "abc");
});
Arbor does not wrap or replace them. Anything that works with Node’s IncomingMessage and ServerResponse works here.
url
url is a URL instance constructed from the request URL and the protocol and host headers.
const actionQuery = createAction(({ url }) => {
const page = url.searchParams.get("page") ?? "1";
return { page: Number(page) };
});
This is the recommended way to read query strings.
context
context is a plain object scoped to the request.
context: Record<string, unknown>;
Best practice is:
- assign freely
- assert when reading
interface ContextUser {
user?: User;
}
const actionAuth = createAction(({ context }) => {
context.user = { id: "123" };
});
const actionProfile = createAction<ContextUser>(({ context }) => {
if (!context.user) throw Ex.InternalServerError("Missing user");
return context.user;
});
Every request gets a fresh context object.
params
params contains route parameters and wildcard segments, all as strings.
const actionUser = createAction(({ params }) => {
const id = params.userId;
return { id };
});
Arbor does not coerce parameter types. Use Number(...) or custom parsing when needed. For wildcard routes (/**), Arbor uses a wild parameter.
const actionStatic = createAction(({ params }) => {
const path = params.wild;
// "/some/path/here"
});
methods
methods is the list of allowed HTTP methods for this route, including auto-generated ones such as HEAD when autoHead is enabled.
You can use it when constructing responses like Allow headers or OPTIONS responses.
const actionOptions = createAction(({ methods }) => {
return {
allow: methods,
};
});
In most actions you will not need methods, but it is available when you do.
cookies
cookies is a helper over request and response cookies.
const actionTrack = createAction(({ cookies }) => {
const visit = cookies.get("visit") ?? "0";
const count = Number(visit) + 1;
cookies.set("visit", String(count), { maxAge: 3600 });
});
Reads return string | undefined. Writes accept only strings. Removing a cookie is done by calling cookies.remove, it will set it to an empty string with Max-Age=0.
getBody
getBody parses the request body on demand. It is covered in detail in the Body section. At a high level:
- it reads the request stream
- normalizes fields unless
skipNormalizeis used - supports JSON, urlencoded, multipart and raw buffers
- enforces required fields, arrays, numbers, booleans and validators when configured
Typical usage:
const actionCreate = createAction(async ({ getBody }) => {
const body = await getBody({
trim: true,
required: ["name"],
});
return { created: body.name };
});
Immutability
The bundle object itself is frozen:
Object.freeze({
req,
res,
url,
context,
params,
methods,
cookies,
getBody,
});
You can:
- mutate
res(headers, status, body) - mutate
context - read everything else
You cannot replace top-level properties on the bundle.
Testing with Bundles
createTestBundle builds the same structure for tests.
import { createTestBundle } from "@kequtech/arbor/testing";
import actionExample from "../src/app/example/action.ts";
test("actionExample works", async () => {
const bundle = createTestBundle({
method: "GET",
url: "/example",
});
const result = await actionExample(bundle);
expect(result).toEqual({ ok: true });
});
This makes it possible to test actions directly without routing.
Summary
The bundle is the core execution context for every request. It contains:
- raw Node objects (
req,res) - high-level helpers (
url,cookies,getBody) - request-scoped
context - routing data (
params,methods)
If you understand the bundle, you understand how Arbor sees each request.