Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

createRequestContext

Creates a request's RequestContext (session, services, platform, requestId and signal) for a custom host or to test an endpoint without a server.

advanced API Since 1.0.0 @meekojs/meeko/server Source code
ts
import { createRequestContext } from "@meekojs/meeko/server";

createRequestContext builds the context received as context by API and RPC handlers, loaders and <script server>: the request, a new requestId, the already-loaded session, services, platform, a request-specific state Map and a signal aborted when the client disconnects or the session is revoked.

createServerApp and createServerDispatcher create it automatically. Use it in a custom host dispatching endpoints individually (dispatchApiRoute, dispatchRpc) and, especially, to test an endpoint in a unit test.

Usage#

ts
import { createRequestContext, MemorySessionStore, SessionManager } from "@meekojs/meeko/server";

const sessions = new SessionManager({ store: new MemorySessionStore() });
const request = new Request("http://localhost/api/report");
const context = createRequestContext(request, { db: { name: "test" } }, await sessions.load(request));

console.log(context.requestId);        // a new UUID
console.log(context.platform.name);    // "node"
console.log(context.services.db.name); // "test"

Type#

ts
function createRequestContext<TServices, TEnv = unknown>(
  request: Request,
  services: TServices,
  session: SessionHandle<any>,
  platform?: ServerPlatform<TEnv>,
): RequestContext<TServices, TEnv>

RequestContext

ts
interface RequestContext<out TServices = unknown, TEnv = unknown> {
	readonly request: Request
	readonly requestId: string
	readonly signal: AbortSignal
	readonly services: ContextServices<TServices>
	readonly session: SessionHandle<SessionData>
	readonly state: Map<string, unknown>
	readonly platform: ServerPlatform<TEnv>
	readonly idempotencyKey?: string | undefined
	readonly clientAddress?: string | undefined
}

Parameters#

Parameter Type Default Description
request Request required The request. Its signal is combined with the context's.
services TServices required The already-created context.services services.
session SessionHandle required The result of sessions.load(request).
platform ServerPlatform<TEnv> { name: "node", env: {} } The adapter's name, environment and waitUntil. Without it, the environment is empty.

Return value#

A RequestContext<TServices, TEnv>:

Member Description
request The original request.
requestId A new ID, sent in x-request-id and received by onError.
signal Aborted if the client disconnects or, with SessionRevokedError, if the session family is revoked. Combined on first access.
services The received services. Since 1.7, without a type argument it is AppServices, the type meeko check infers from server/runtime.ts.
session The session: current, create, rotate, revoke…
state An empty Map for this request's values (useRequestState).
platform The received platform.
idempotencyKey The Idempotency-Key header if it is a UUID, lowercased; otherwise, undefined.
clientAddress The client's IP. undefined in a manually created context: adapters provide it.

Examples#

Test an endpoint without a server#

ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { createRequestContext, defineApiRoute, dispatchApiRoute, MemorySessionStore, SessionManager } from "@meekojs/meeko/server";
import type { Schema } from "@meekojs/meeko/server";

const report: Schema<{ region: string }> = { "~standard": { version: 1, validate: (value) => ({ value: value as { region: string } }) } };
interface Services { readonly region: string }

// In an application, .meeko/types/services.d.ts provides the context.services type; here it is specified manually.
const route = defineApiRoute<undefined, undefined, undefined, typeof report, Services>({
  response: report,
  handler: ({ context }) => ({ region: context.services.region }),
});

test("the report uses the services' region", async () => {
  const sessions = new SessionManager({ store: new MemorySessionStore() });
  const request = new Request("http://localhost/api/report");
  const context = createRequestContext(request, { region: "eu" }, await sessions.load(request));
  const response = await dispatchApiRoute(route, context, sessions);
  assert.equal(response.status, 200);
  assert.deepEqual(await response.json(), { region: "eu" });
});

dispatchApiRoute applies guards and response validation and normalizes errors, just as in the application.

With the host's platform and environment#

ts
import { createRequestContext, MemorySessionStore, SessionManager } from "@meekojs/meeko/server";

const sessions = new SessionManager({ store: new MemorySessionStore() });
const request = new Request("http://localhost/");
const context = createRequestContext(request, {}, await sessions.load(request), {
  name: "node",
  env: { FEATURE_FLAGS: "beta" },
  waitUntil: (promise) => { void Promise.resolve(promise).catch(console.error); },
});
console.log(context.platform.env.FEATURE_FLAGS); // "beta"

Cancel work when the client leaves#

ts
export default defineApiRoute({
  response: Report,
  handler: async ({ context }) => {
    const upstream = await fetch("https://api.example.com/report", { signal: context.signal });
    return await upstream.json();
  },
});

If the client closes the connection, or another request revokes its session, context.signal is aborted and fetch is canceled.

Common mistakes#

Pass the SessionManager instead of the session#

Incorrectts
const context = createRequestContext(request, services, sessions);

TS2345: the third argument is this request's SessionHandle, not the manager. At runtime, context.session.current would be undefined and session guards would fail.

Correctts
const context = createRequestContext(request, services, await sessions.load(request));

Reuse a context across requests#

Incorrectts
const shared = createRequestContext(firstRequest, services, session);
await dispatchApiRoute(route, shared, sessions);
await dispatchApiRoute(otherRoute, shared, sessions); // another request

A context belongs to one request: context.request (with its body), requestId, signal, session and state belong to the first one. The second endpoint does not see its own request, and both share requestId in logs.

Correctts
for (const request of requests) {
  const context = createRequestContext(request, services, await sessions.load(request));
  await dispatchApiRoute(route, context, sessions);
}

Notes#

  • Server. A synchronous, portable function; load the session first with await sessions.load(request).
  • Types. createRequestContext<TServices, TEnv> infers both types from its arguments. Since 1.7, a RequestContext written without arguments has services: AppServices; with generated .meeko/types/services.d.ts, it is the server/runtime.ts type. A test context with fake services ({ db: falso }) is still accepted in a route handler without a services type, as in 1.6: RequestContext compares by its services type.
  • IP. A manually created context has no clientAddress, so rateLimit without key counts all clients together. Outside a context, clientAddress(request) (since 1.7) gives the IP with the same policy.
  • Security. signal is aborted with SessionRevokedError when the session is revoked mid-request: use it in long operations to avoid finishing work for a session that is no longer valid.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/advanced/createrequestcontext.md