[]previous and next page

Middleware

middleware is a list of functions that wrap the handler, outermost first.

For agentsUpdated

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.

src/server/auth.tsts
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();
};
src/server/account.tsts
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.