[]previous and next page

Middleware and error responses

Handler-wide middleware is (event, next) => Response, listed outermost first in middleware.

For agentsUpdated

Handler-wide middleware is (event, next) => Response, listed outermost first in middleware. It runs after the guards and the CSRF check, so a request those refuse never reaches it, and before the route. await next() always resolves with a Response, also when the rest of the request fails. A route that throws an unexpected error, an HttpError, a redirect() or a failed validation does not reject next(): the handler turns the failure into the response the client will get, inside the chain. Code after await next() therefore runs for a 500, a 401 or a 303 as for a 200, and headers set there are on the error response.

What next() returns for a failure is the final answer for the client, not the error object. In production the answer to an unexpected error is the generic 500 (the detail went to onError once, before your middleware ran), and an HttpError 5xx shows its reason phrase. A middleware that wants another answer returns a different Response, or throws its own error, which is mapped like any other (throw new HttpError(503, ...)). Calling next() twice is still an error (MIDDLEWARE_NEXT_TWICE, answered 500). The same holds for the middleware export of an API route and for the middleware option of apiHandler. Server function middleware (serverFn({ middleware })) is a different kind: it wraps the function and sees its value or its thrown error, not a Response.

The session and rate limit middleware rely on it: the session is saved, and its cookie set, also when the route answered with an error. The next example stamps every answer with the time the route took, errors included.

src/timing.tsts
import type { Middleware } from "@shadow-js/dusk/server";

/** Adds `server-timing` to every response, also to the answer for a thrown error. */
export const timing: Middleware = async (_event, next) => {
  const started = performance.now();
  const response = await next();
  response.headers.set("server-timing", `app;dur=${Math.round(performance.now() - started)}`);
  response.headers.set("x-status-seen", String(response.status));
  return response;
};