createLocalServerFetch
The local dispatch used by api and rpc clients during SSR, serving calls through the HTTP pipeline without leaving the process and only with the current request's origin and credentials.
import { createLocalServerFetch } from "@meekojs/meeko/server";createLocalServerFetch(options) creates a ServerFetch: a (request, context) => Promise<Response> function that handles a request to /api or /_meeko/rpc through the application's pipeline, inside the process, reusing the current request's session, services, state and requestId. It underlies api.* and rpc.* while the server renders a page: the page calls its own API without a network or second session.
Because it reuses the session from context, it only accepts requests with the same origin and credentials: any others receive 403. This prevents anyone from using it to communicate with the API on behalf of another user.
Usage#
import { pathToFileURL } from "node:url";
import { createLocalServerFetch } from "@meekojs/meeko/server";
const serverFetch = createLocalServerFetch({ manifest, sessions, load: (file) => import(pathToFileURL(file).href) });
// Same origin and credentials as the original request.
const request = new Request(new URL("/api/users", context.request.url), { headers: context.request.headers });
const response = await serverFetch(request, context);Type#
function createLocalServerFetch<S>(
options: Pick<ServerDispatcherOptions<S>, "manifest" | "sessions" | "load" | "onError">,
): ServerFetchtype ServerFetch = (request: Request, context: RequestContext) => Promise<Response>Parameters#
createLocalServerFetch(options):
| Option | Type | Default | Description |
|---|---|---|---|
manifest |
ServerManifest |
required | The API and RPC endpoints. |
sessions |
SessionManager |
required | The application's manager. |
load |
(file: string) => Promise<unknown> |
required | Loads each manifest file's module. |
onError |
(error, requestId) => void |
none | Observer for 5xx failures. |
Return value#
A ServerFetch: (request: Request, context: RequestContext) => Promise<Response>.
| Request | Response |
|---|---|
Origin different from context.request |
403 INVALID_ORIGIN |
cookie or authorization different from context.request |
403 INVALID_CREDENTIALS |
| An API or RPC route | The endpoint's response, with the usual guards and this request's Idempotency-Key. |
Examples#
Measure a render's internal calls#
import type { ServerFetch } from "@meekojs/meeko/server";
export const logged = (inner: ServerFetch): ServerFetch => async (request, context) => {
const start = performance.now();
const response = await inner(request, context);
console.log(context.requestId, request.method, new URL(request.url).pathname, response.status, `${Math.round(performance.now() - start)} ms`);
return response;
};A typed server client at module level#
import { createApiClient } from "@meekojs/meeko/framework";
import { createServerClientOptions } from "@meekojs/meeko/server";
// Each call reads the URL, credentials and local dispatch from the current request:
// the client never retains another user's credentials.
export const api = createApiClient<ApiContract>(manifest, createServerClientOptions());This is how the generated api and rpc clients work for SSR. Outside a Meeko request, each call throws an error.
Common mistakes#
Forward a different cookie#
const request = new Request(new URL("/api/admin/users", context.request.url), { headers: { cookie: adminCookie } });
await serverFetch(request, context);Responds with 403 {"code":"INVALID_CREDENTIALS"}: local dispatch shares the session from context and cannot change it. To act as another user, make a real HTTP request.
const request = new Request(new URL("/api/admin/users", context.request.url), { headers: context.request.headers });
await serverFetch(request, context);Call another host#
await serverFetch(new Request("https://api.otro.example/users", { headers: context.request.headers }), context);Responds with 403 {"code":"INVALID_ORIGIN"}. For an external service, use fetch with context.signal.
await fetch("https://api.otro.example/users", { signal: context.signal });Notes#
- Security. Comparing origin and credentials prevents local dispatch from being used as a proxy with someone else's session.
requestId. The call shares the original request's ID: logs andonErrorgroup the render and its calls.- Origin. SSR
api.*andrpc.*clients send the current request's origin, so the mutationOrigincheck (enabled by default since1.8) also works locally.