Middleware
middleware is a list of functions that wrap the handler, outermost first.
middleware is a list of functions that wrap the handler, outermost first. Each receives { event, input } (the
input has already passed the schema) and next(). Return next()'s value to continue, return another value to
answer without running the rest, or throw an HttpError to reject. Calling next() twice rejects with a
DuskError whose code is MIDDLEWARE_NEXT_TWICE. These are not the handler-wide Middleware
from createHandler({ middleware }), which wraps every request and runs earlier; see
Dusk: security.
The order for one call is: schema, then each middleware, then the handler. A request with invalid input is therefore refused with 400 before an authorization middleware sees it.
Middleware passes data to the handler through the event. createKey<T>(name) makes a typed key;
event.set(key, value) stores a value, event.get(key) reads it (and throws CONTEXT_KEY_UNSET when it was
never set), and event.tryGet(key) returns undefined instead. requireUser reads a cookie; a real app would
read a session.
import { createKey, HttpError, type ServerFnMiddleware } from "@shadow-js/dusk/server";
export interface User {
id: string;
}
/** Where `requireUser` leaves the signed-in user for the handler. */
export const userKey = createKey<User>("user");
export const requireUser: ServerFnMiddleware = async ({ event }, next) => {
const id = event.cookies.get("uid");
if (id === undefined) throw new HttpError(401, "Sign in first");
event.set(userKey, { id });
return next();
};
import { getRequestEvent, HttpError, serverFn } from "@shadow-js/dusk/server";
import { noteId } from "../schemas";
import { requireUser, userKey } from "./auth";
import { notes } from "./db.server";
export const removeNote = serverFn(
{ input: noteId, middleware: [requireUser] },
async ({ id }, event): Promise<void> => {
const note = notes.get(id);
if (note === undefined) throw new HttpError(404, "No such note");
if (note.owner !== event.get(userKey).id) throw new HttpError(403, "Not your note");
notes.delete(id);
},
);
export const whoami = serverFn({ method: "GET", middleware: [requireUser] }, async (_input, event) => ({
id: event.get(userKey).id,
route: event.route.kind,
sameEvent: getRequestEvent() === event,
}));
getRequestEvent() returns the event of the request being handled from anywhere in the call, in middleware and in
helpers the handler calls, and it is the same object as the event argument. It needs AsyncLocalStorage;
where the runtime has none it throws SERVER_ALS_UNAVAILABLE, and outside a request SERVER_NO_REQUEST_EVENT.
Calling a server function directly uses it, so a direct call outside a request fails; invoke(input, event) takes
the event explicitly and works anywhere.