Advanced

Middleware and request flow

Middleware is code that runs between a request arriving and your route responding. Learn to chain middleware for logging, parsing forms, and protecting routes before they execute.

What you will see

Middleware and request flow Stage 1: express.json() -> continue Stage 2: logger -> continue Stage 3: requireAuth -> continue Final request body: {"q": "hi"}

About 2 min of reading

Try It

Express runs middleware in order

1

Run each middleware stage in order.

def run_middleware(middleware, req): state = dict(req) for i, mw in enumerate(middleware, 1): if mw["fn"](state): print("Stage %d: %s -> stops" % (i, mw["name"])) return state print("Stage %d: %s -> continue" % (i, mw["name"])) return state print() print("Middleware and request flow") middleware = [ {"name": "express.json()", "fn": lambda r: r.update({"body": r.get("raw", {})}) or False}, {"name": "logger", "fn": lambda r: False}, {"name": "requireAuth", "fn": lambda r: False}, ] result = run_middleware(middleware, {"raw": {"q": "hi"}}) print("Final request body: %s" % json.dumps(result.get("body")))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Middleware and request flow Stage 1: express.json() -> continue Stage 2: logger -> continue Stage 3: requireAuth -> continue Final request body: {"q": "hi"}
Express runs middleware in the exact order you add it.

Data Flow

A request flows through each middleware in turn; each one may inspect it, modify it, or hand control to the next handler.
1 Request arrives at the server
↓
2 First middleware parses the body
↓
3 Second middleware logs the request
↓
4 Third middleware checks authentication
↓
5 next() passes control to the route
↓
6 Route sends the response
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

def run_middleware(middleware, req): sets nothing on screen
state = dict(req) sets nothing on screen
for i, mw in enumerate(middleware, 1): sets nothing on screen
if mw["fn"](state): sets nothing on screen
print("Stage %d: %s -> stops" % (i, mw["name"])) Middleware and request flow
return state sets nothing on screen
print("Stage %d: %s -> continue" % (i, mw["name"])) Stage 1: express.json() -> continue
return state sets nothing on screen
print() Stage 2: logger -> continue
print("Middleware and request flow") Stage 3: requireAuth -> continue
middleware = [ sets nothing on screen
{"name": "express.json()", "fn": lambda r: r.update({"body": r.get("raw", {})}) or False}, sets nothing on screen
{"name": "logger", "fn": lambda r: False}, sets nothing on screen
{"name": "requireAuth", "fn": lambda r: False}, sets nothing on screen
] sets nothing on screen
result = run_middleware(middleware, {"raw": {"q": "hi"}}) sets nothing on screen
print("Final request body: %s" % json.dumps(result.get("body"))) Final request body: {"q": "hi"}
Why this works
The order of middleware matters

Express runs middleware in the exact order you add it. That sounds simple but it is the entire control flow of a server.

app.use(express.json());   // 1. parse the body
app.use(logger);           // 2. log the request
app.use(requireAuth);      // 3. check login
app.get('/', handler);     // 4. finally the route

If you put express.json() after the route, your req.body would still be empty when the route runs. Understanding ordering, and reading next() calls like a story from top to bottom, is what makes debugging Express possible.

Examples

Your first middleware

Middleware is a function that gets req, res, and a special next.

function logger(req, res, next) {
  console.log('Request at', new Date().toISOString());
  next();   // pass control to the next handler
}

app.use(logger);

next() is the key. Call it to let the request continue down the chain. Call it with an error (below) to stop and jump to error handling. This ordering is what lets you attach behaviour like logging or auth to every route.

Protecting routes

A common pattern: a middleware that checks if a user is logged in, and blocks the rest if not.

function requireAuth(req, res, next) {
  if (req.session.user) {
    return next();
  }
  res.status(401).send('Please log in');
}

app.get('/dashboard', requireAuth, showDashboard);

Insert requireAuth before any route that must be private. The route only runs for people who passed the check. This is how whole layers of protection attach to a route.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← CRUD with SQL
Previous Lesson
CRUD with SQL
Next Lesson
Authentication and passwords
Authentication and passwords →