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 = fn returns [fn]
  • export const middlewares = [fnA, fnB] returns [fnA, fnB]
  • export const mw = ... follows the same pattern as middleware

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 in src/js/server/middleware.js
  • Order preservation: The loader maintains the exact array order from exports (default, middlewares, middleware, or mw)
  • Sequential execution: Middleware runs in array order via for...of loops in src/js/runtime/handler.js (dev) and src/js/server/prod/core-handler.js (prod)
  • Short-circuiting: Returning a Response stops 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →