Express Middleware

Reviewed & published by Brayan K

Middleware is a function that runs between the incoming request and your final response, with the signature (req, res, next), letting you log, parse, authenticate, or transform every request in a pipeline.

Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll write your own middleware, understand why order matters, share data down the chain with req, use built-in parsers like express.json(), and add a catch-all error handler.

What You'll Learn in This Lesson

1️⃣ What Middleware Is

A middleware function takes three arguments: req (the request), res (the response), and next (a function that hands control to whatever comes next). Register it with app.use() and it runs for every request. The golden rule: always either call next() or send a response.

// MIDDLEWARE is a function that runs IN THE MIDDLE — between the incoming
// request and your final response. Its signature is always (req, res, next).
const express = require("express");
const app = express();

// app.use registers middleware that runs for EVERY request.
// It receives the request (req), the response (res), and next().
app.use((req, res, next) => {
  console.log("Request received:", req.method, req.url);
  // You MUST call next() to pass control to the next handler.
  // Forget it, and the request hangs forever.
  next();
});

app.get("/", (req, res) => {
  res.send("Hello from the route handler!");
});

app.listen(3000, () => console.log("Server on http://localhost:3000"));

// When a browser requests GET /, the middleware logs the line FIRST,
// calls next(), and then the route handler sends the response.

The middleware logged the request, then called next() to let the route handler send the actual reply. Drop the next() and the browser would spin forever — the request would never reach the handler.

2️⃣ Order Matters: a Pipeline

Middleware runs in registration order, like a pipeline. Each function can attach data to req so later functions — including your route handler — can read it. This is how things like the logged-in user or a request timer get shared down the chain.

// Middleware runs IN THE ORDER you register it. Think of it as a pipeline:
// each function can read/modify req and res, then pass the baton with next().
const express = require("express");
const app = express();

// 1) A timing logger — records when the request started.
app.use((req, res, next) => {
  req.startTime = Date.now();      // attach data to req for later middleware
  console.log("1. Logger: " + req.method + " " + req.url);
  next();
});

// 2) A fake "auth" check — adds a user, then continues.
app.use((req, res, next) => {
  req.user = { name: "Sam" };      // share data down the chain
  console.log("2. Auth: attached user " + req.user.name);
  next();
});

// 3) The route handler can read everything the middleware added.
app.get("/", (req, res) => {
  const ms = Date.now() - req.startTime;
  res.send("Hi " + req.user.name + " (handled in " + ms + "ms)");
});

app.listen(3000);

The logger ran first, then auth, then the handler — exactly the order they were registered. Because each stored data on req, the final handler could read both req.startTime and req.user.

3️⃣ Built-in and Error-Handling Middleware

Express bundles useful middleware. express.json() parses a JSON request body onto req.body. And a special error-handling middleware — recognized by its four arguments (err, req, res, next) — catches anything that throws, so you can return a clean error instead of crashing.

// Express ships with BUILT-IN middleware. express.json() parses an incoming
// JSON body and puts the result on req.body — without it, req.body is undefined.
const express = require("express");
const app = express();

app.use(express.json());           // built-in body parser for JSON

app.post("/echo", (req, res) => {
  // Thanks to express.json(), req.body is a real object here.
  res.json({ youSent: req.body });
});

// ERROR-HANDLING middleware has FOUR arguments: (err, req, res, next).
// Express recognizes it by the four-argument signature and calls it
// whenever a handler throws or passes an error to next(err).
app.get("/boom", (req, res) => {
  throw new Error("Something broke");
});

app.use((err, req, res, next) => {
  console.error("Caught:", err.message);
  res.status(500).json({ error: err.message });
});

app.listen(3000);

// POST /echo with body {"hi":1}  ->  {"youSent":{"hi":1}}
// GET  /boom                     ->  500 {"error":"Something broke"}

Your turn. Fill in the two ___ blanks to complete a logger and a timestamp middleware, then run it.

// 🎯 YOUR TURN — finish the middleware, then run: node app.js
const express = require("express");
const app = express();

// TODO 1: write a middleware that logs the request method and url,
//         then calls next(). Remember the (req, res, next) signature.
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  ___;   // 👉 pass control to the next handler
});

// TODO 2: add a middleware that puts the current time on req.now,
//         then continues the chain.
app.use((req, res, next) => {
  req.now = ___;        // 👉 a Date or timestamp
  next();
});

app.get("/", (req, res) => {
  res.send("Request handled at " + req.now);
});

app.listen(3000, () => console.log("Listening on 3000"));

// ✅ Expected log when you visit GET / :
//    GET /

Mini-Challenge: An API Key Gate

Write a middleware that only lets the request through when ?key=secret is present, and rejects everything else with a 401. Register it before your routes.

// 🎯 MINI-CHALLENGE: a tiny "API key" gate.
// Save as gate.js and run: node gate.js
//
// 1. Write a middleware checkKey(req, res, next) that looks at
//    req.query.key (the ?key=... part of the URL).
// 2. If the key equals "secret", call next() to allow the request.
// 3. Otherwise respond res.status(401).json({ error: "no access" })
//    and do NOT call next().
// 4. Register it with app.use(checkKey) BEFORE your routes.
//
// ✅ Expected:
//    GET /data?key=secret  -> 200 {"ok":true}
//    GET /data             -> 401 {"error":"no access"}

const express = require("express");
const app = express();

// your code here

app.get("/data", (req, res) => res.json({ ok: true }));
app.listen(3000);

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Express Middleware

GoalSyntax
Global middlewareapp.use((req, res, next) => {})
Pass control onnext()
Parse JSON bodyapp.use(express.json())
Path-scopedapp.use("/api", mw)
Send an error onwardnext(err)
Error handler(err, req, res, next) => {}

🎉 Lesson Complete!

Practice quiz

What is the standard signature of an Express middleware function?

  • (req, res, next)
  • (request)
  • (next, req)
  • (err, data)

Answer: (req, res, next). Middleware receives the request, the response, and next().

What must you call to pass control to the next handler in the chain?

  • continue()
  • next()
  • pass()
  • done()

Answer: next(). Calling next() hands control to the following middleware or route.

What happens if a middleware forgets to call next() (and never responds)?

  • Express auto-calls next
  • It skips to the route
  • It logs a warning only
  • The request hangs forever

Answer: The request hangs forever. Without next() or a response, the request never completes and hangs.

Which method registers middleware that runs for EVERY request?

  • app.get()
  • app.route()
  • app.use()
  • app.all()

Answer: app.use(). app.use() registers middleware that runs on every incoming request.

In what order does middleware run?

  • In the order it is registered
  • Alphabetically
  • Randomly
  • Reverse of registration

Answer: In the order it is registered. Middleware executes as a pipeline, in registration order.

Which built-in middleware parses an incoming JSON request body?

  • express.text()
  • express.json()
  • express.raw()
  • express.body()

Answer: express.json(). express.json() parses JSON bodies and puts the result on req.body.

Without express.json(), what is req.body for a JSON POST?

  • An empty array
  • The raw string
  • undefined
  • An error object

Answer: undefined. Without a body parser, req.body is undefined.

How does Express recognize ERROR-handling middleware?

  • By a special name
  • By a decorator
  • By a config flag
  • By its four arguments (err, req, res, next)

Answer: By its four arguments (err, req, res, next). The four-argument signature (err, req, res, next) marks it as error middleware.

How can one middleware share data with later handlers?

  • By attaching it to the req object
  • Using a global variable only
  • It cannot share data
  • By returning it

Answer: By attaching it to the req object. Attaching properties to req (e.g. req.user) passes data down the chain.

Which status code does the example error handler send with res.status(...)?

  • 200
  • 500
  • 404
  • 201

Answer: 500. The error handler responds with res.status(500) for a server error.

Continue this course

Frequently asked questions

What exactly is middleware in Express?

Middleware is simply a function that sits in the middle of the request–response cycle. Every middleware function receives three arguments: req (the request), res (the response), and next (a function that passes control to the next middleware). Inside it you can read or modify the request and response, run side effects like logging or authentication, and then either call next() to continue or end the cycle by sending a response. Express processes middleware in the order you register them, forming a pipeline each request flows through.

Why does my request hang and never respond?

Almost always because a middleware forgot to call next() and also did not send a response. Express has no way to know you are done, so it waits forever. Every middleware must do exactly one of two things: call next() to pass control along, or end the cycle by calling res.send(), res.json(), res.end(), etc. If a middleware does asynchronous work, make sure next() is called after that work completes, not before — and watch out for code paths (like an early return) that skip it.

How is error-handling middleware different from regular middleware?

Error-handling middleware takes four arguments instead of three: (err, req, res, next). Express specifically looks at the number of parameters to recognize it, so the four-argument shape is required. It only runs when an error is thrown in a synchronous handler or passed explicitly with next(err). You typically register one error handler last, after all your routes, so it acts as a catch-all that logs the problem and sends a clean error response instead of leaking a stack trace to the client.

What is the difference between app.use and app.get?

app.use registers middleware that runs for requests of any HTTP method, and (if you give no path) for every path — it is for cross-cutting concerns like logging, parsing bodies, or auth. app.get (and app.post, app.put, etc.) registers a handler for one specific method and path, and is where you produce the actual response for that route. Under the hood a route handler is just middleware too; the difference is that app.get is scoped to a method and URL, while app.use is broad.