# How Middleware Execution Order Works in SXO's Middleware Chain

> Understand SXO middleware execution order in the middleware chain. Learn how SXO runs middleware sequentially and short-circuits the chain for efficient request processing. Optimize your SXO setup.

- Repository: [Víctor García/sxo](https://github.com/gc-victor/sxo)
- Tags: internals
- Published: 2026-03-02

---

**SXO executes middleware in the exact order they are exported from [`middleware.js`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/src/js/runtime/handler.js)](../src/js/runtime/handler.js)) and production ([[`src/js/server/prod/core-handler.js`](https://github.com/gc-victor/sxo/blob/main/src/js/server/prod/core-handler.js)](../src/js/server/prod/core-handler.js)), the execution follows this pattern:

```javascript
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:

```javascript
// 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:

```javascript
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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/src/js/runtime/handler.js) (dev) and [`src/js/server/prod/core-handler.js`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/middleware.js) export. Since SXO preserves the exact array order when loading via [`src/js/server/middleware.js`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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`](https://github.com/gc-victor/sxo/blob/main/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.