How Middleware Execution Order Works in SXO's Middleware Chain
SXO executes middleware in the exact order they are exported from middleware.js, running sequentially through an array and short-circuiting the chain when any middleware returns a Response object.
SXO (Serverless XO) is an open-source framework that processes HTTP requests through a customizable middleware chain. Understanding the middleware execution order is essential for controlling request flow, authentication, and error handling in your SXO applications.
How SXO Discovers and Orders Middleware
SXO loads user-defined middleware from the project root file SRC_DIR/middleware.{js,ts,mjs}. The loader logic resides in [src/js/server/middleware.js](../src/js/server/middleware.js#L38-L68) and returns an array of functions in the exact order they appear in the source file.
The loader normalizes various export patterns by checking mod.default ?? mod.middlewares ?? mod.middleware ?? mod.mw. If the exported value is an array, it filters for functions while preserving the original order. This means the final middleware chain always reflects the order written by the developer.
Supported Export Patterns
The loader handles multiple export styles consistently:
export default function ...returns[defaultFn]export default [fnA, fnB]returns[fnA, fnB]export const middleware = fnreturns[fn]export const middlewares = [fnA, fnB]returns[fnA, fnB]export const mw = ...follows the same pattern asmiddleware
Sequential Execution and Short-Circuiting Behavior
When a request reaches the server, the core handler iterates over the middleware array sequentially. In development ([src/js/runtime/handler.js](../src/js/runtime/handler.js)) and production ([src/js/server/prod/core-handler.js](../src/js/server/prod/core-handler.js)), the execution follows this pattern:
for (const mw of middlewares) {
if (typeof mw !== "function") continue;
const result = await mw(request, env);
if (result instanceof Response) return result; // short-circuit
}
Sequential execution guarantees that the first middleware runs first, then the second, and so on. Short-circuiting occurs when any middleware returns a Response object, causing the chain to stop immediately and return that response to the client.
Error Handling During Execution
Error behavior differs between environments:
- Development: Thrown errors are caught and return a generic 500 response
- Production: Errors are logged but do not stop the middleware chain; the handler proceeds to the next item in the array
This means a failing middleware in production allows subsequent middleware to run, while development mode fails fast.
Practical Implementation Examples
Ordered Middleware with Authentication
This example demonstrates how order affects execution, with logging running first, authentication potentially short-circuiting, and custom headers only applying after successful auth:
// middleware.js (project root)
export const middlewares = [
// 1️⃣ Logging – runs first
async (req) => {
console.log("→", req.method, req.url);
},
// 2️⃣ Auth – may short-circuit with 401
async (req) => {
if (!req.headers.get("Authorization")) {
return new Response("Missing auth", { status: 401 });
}
},
// 3️⃣ Custom header – runs only if auth succeeds
async (req, env) => {
env.custom = "value";
},
];
Loading Middleware in Development Server
When creating a development server handler, the loader returns the ordered array:
import { loadUserDefinedMiddlewares } from "./middleware.js";
const userMiddlewares = await loadUserDefinedMiddlewares();
const handler = createCoreHandler({
routes,
modules,
publicPath,
middleware: userMiddlewares,
});
Summary
- Source location: SXO loads middleware from
SRC_DIR/middleware.{js,ts,mjs}using the loader insrc/js/server/middleware.js - Order preservation: The loader maintains the exact array order from exports (
default,middlewares,middleware, ormw) - Sequential execution: Middleware runs in array order via
for...ofloops insrc/js/runtime/handler.js(dev) andsrc/js/server/prod/core-handler.js(prod) - Short-circuiting: Returning a
Responsestops the chain immediately - Error resilience: Production continues to next middleware on error; development returns 500
Frequently Asked Questions
How do I change the execution order of middleware in SXO?
Reorder the array in your middleware.js export. Since SXO preserves the exact array order when loading via src/js/server/middleware.js, moving functions earlier or later in the array directly changes their execution priority. For single function exports, only one middleware runs, so convert to an array format to control ordering.
Can async middleware functions affect execution order?
Yes, but sequentially. Each middleware must await completion before the next begins. The handler in src/js/runtime/handler.js uses await mw(request, env) inside a for...of loop, ensuring that async operations complete in order. However, if an async middleware returns a Response, it short-circuits immediately without waiting for subsequent middleware.
What happens if a middleware throws an error in production?
The chain continues. According to src/js/server/prod/core-handler.js, thrown errors are caught and logged, but the handler proceeds to the next middleware in the array. This differs from development mode (src/js/runtime/handler.js), where errors typically return a generic 500 response and stop processing.
How does SXO handle multiple export styles without breaking execution order?
The loader normalizes different export patterns into a single ordered array. In src/js/server/middleware.js, the loader checks mod.default ?? mod.middlewares ?? mod.middleware ?? mod.mw, flattens the result, filters for functions, and preserves the original sequence. This means whether you use export default, export const middlewares, or export const mw, the execution order remains deterministic based on your source code arrangement.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →