NodeJSWhat is Middleware?

What is Middleware?

Middleware is the central idea in Express — and once it clicks, the whole framework makes sense. A middleware is simply a function that runs during the request/response cycle, with access to req, res, and a next function that passes control to the next middleware. Requests flow through an ordered pipeline of these functions; each can inspect, modify, respond, or hand off. Routes, body parsers, auth, logging, error handling — in Express, everything is middleware.

The signature

JS
function middleware(req, res, next) {
  // 1. Do something with req / res
  // 2. Then EITHER:
  next()              //   pass control to the next middleware, OR
  // res.send(...)    //   send a response and end the cycle
}
Three parameters: `req`, `res`, `next`
A normal middleware takes `(req, res, next)`. `req` and `res` are the same objects that flow through the whole pipeline; `next` is a function you call to continue to the next middleware. (Error-handling middleware takes a *fourth* parameter — `(err, req, res, next)` — covered in [error middleware](/nodejs/error-middleware).) A route handler is just middleware that's matched to a method and path.
The pipeline model

Picture the request passing through a series of functions in registration order. Each calls next() to continue. The first one to send a response stops the flow:

Text
Incoming request
      │
      ▼
[ logger ] ──next()──▶ [ express.json ] ──next()──▶ [ auth ] ──next()──▶ [ route handler ]
                                                         │                       │
                                              (no token? respond 401)     (sends the response)
                                                         │
                                                  cycle ends early
Middleware runs top-to-bottom — registration order is execution order
Express executes middleware in the **exact order you register it**. This single rule explains most Express behavior: `express.json()` must come before handlers that read `req.body`; auth must come before protected routes; the error handler must come last. A middleware registered too late simply never runs for the requests it was meant to handle. When something "isn't working," check the order first.
next() — the control-flow lever

JS
const app = express()

app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`)   // runs for every request
  next()                                       // → continue to next middleware
})

app.get('/', (req, res) => {
  res.send('Home')                             // sends response, cycle ends
})
Forgetting `next()` hangs the request forever
A middleware that neither responds nor calls `next()` leaves the request **stuck** — the client waits until it times out, and the route handler never runs. The two valid endings are: call `next()` (continue) or send a response (`res.send`/`res.json`/etc., which ends the cycle). Do exactly one. Calling `next()` *and* responding can trigger "headers already sent."
Passing errors with next(err)

Calling next() with an argument is special: it skips all remaining normal middleware and jumps straight to the error handler:

JS
app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await db.find(req.params.id)
    if (!user) {
      const err = new Error('Not found')
      err.status = 404
      return next(err)             // → jump to error-handling middleware
    }
    res.json(user)
  } catch (err) {
    next(err)                      // forward unexpected errors too
  }
})
`next(err)` routes to error middleware; `next()` continues normally
Passing *any* argument to `next` tells Express "something went wrong" — it bypasses normal middleware and runs your [4-argument error handler](/nodejs/error-middleware). Plain `next()` continues the normal flow. In Express 5, a thrown error or rejected promise in an `async` handler is forwarded to `next(err)` automatically.
The types of middleware

Type

Scope

Registered with

Application-level

Whole app

app.use() / app.METHOD()

Router-level

One router

router.use()

Built-in

Ships with Express

express.json(), express.static()

Third-party

From npm

app.use(cors()), helmet()

Error-handling

Catches errors

(err, req, res, next)

Why this design is powerful
  • Composability — build complex behavior from small, single-purpose functions.

  • Reusability — the same logging/auth middleware works across routes and projects.

  • Separation of concerns — cross-cutting concerns (auth, logging, parsing) live outside handlers.

  • Ecosystem — thousands of npm middleware packages plug straight in.

Next
Apply middleware across your entire app: [Application-Level Middleware](/nodejs/application-middleware).