Middleware Fundamentals

By Syncfusion®, Inc. · Last updated

What middleware is, the (req, res, next) pipeline, the three ways to finish, middleware types, global, path-based and route-specific mounting, registration order, custom middleware, access-control middleware, factories and async middleware in Express 5.

What you will learn

  • Explain how the middleware pipeline runs
  • Finish middleware with next, a response or next(err)
  • Mount middleware globally, on a path or on a route
  • Write custom and configurable middleware
  • Handle errors from async middleware in Express 5

Middleware Introduction

Middleware = a function that runs on a request before (or instead of) your route.

  • It has three inputs: (req, res, next).
  • next = a function that passes the request to the next step.
  • Middleware can read req, change req and res, or send an answer.
  • Express runs middleware in the order you add it.
  • Logging, body parsing, login checks and error pages are all middleware.

Simple lesson: Middleware is a checkpoint every request walks through.

A Real-World Example

Think about the school morning line.

  1. The gate guard checks your ID card.
  2. The class teacher marks attendance.
  3. The monitor checks your uniform.
  4. Then you reach your classroom.

Any checkpoint can stop you: "No ID card, go home". Each checkpoint is middleware. The classroom is your route.

In this lesson, you will learn:

  1. The shape of a middleware function.
  2. The three ways middleware can finish.
  3. Middleware types and where to mount them.
  4. Why order matters.
  5. How to write custom and reusable middleware.
  6. How async middleware handles errors in Express 5.

The Middleware Pipeline

Animation: a request passes a logger, a login check and a route; a second request is stopped at the login check with 401

logger

requireLogin

GET /orders

200 OK

401 Unauthorized

next() next() res.send res.status(401)

Blue request: every step calls next(). Orange request: the login check answers and the trip ends.

Request-response pipeline = the line of middleware and routes a request passes through.

  • Each step either passes the request on or ends the trip.
  • The order of steps is the order of your app.use calls.
  • Routes are the last stop in the line.

Simple lesson: Requests flow down the pipeline until something answers.

Three Ways to Finish

Every middleware must do one of these three things:

  • Call next() = "I am done, pass it on".
  • Send a response = "I answered, stop here". Use return so nothing else runs.
  • Call next(error) = "Something broke". Express jumps to error middleware.
  • If it does none of these, the request hangs. The client waits forever.
  • The first request has the header, so it passes to the route.
  • The second gets 401 from the middleware. The route never runs.
  • /broken reaches the error handler and gets 500.
  • /stuck never gets an answer.

A hanging request is hard to spot, because nothing crashes. If a client spins forever, look for middleware that forgot next().

Simple lesson: Call next(), send a response, or pass an error. Never nothing.

Middleware Types

  • Application-level: app.use(fn) or app.get(path, fn, handler).
  • Router-level: router.use(fn). See Routing and Routers.
  • Built-in: comes with Express, such as express.json() and express.static().
  • Third-party: installed with npm, such as helmet, cors and morgan.
  • Error-handling: has four inputs: (err, req, res, next).

Simple lesson: Same idea everywhere. Only the place you mount it changes.

Mounting Middleware

  • Global: app.use(fn) runs for every request.
  • Path-based: app.use("/admin", fn) runs for /admin and everything under it.
  • Route-specific: app.get("/orders", fn, handler) runs for that one route.
  • Many at once: app.use(fnA, fnB) or app.get(path, [fnA, fnB], handler).
  • Middleware stack = all the middleware in order, for one path.
  • Watch the terminal for each request.
  • [global] prints every time. The others print only on their paths.

Simple lesson: Mount middleware as narrowly as the job needs.

Middleware Order

  • Express runs middleware in registration order: the order of your code.
  • Register middleware before the routes that need it.
  • express.json() after a route means that route sees no body.
  • Register error middleware after all routes.
  • The answer says "nothing", because the body was never parsed.
  • Move app.use(express.json()) above the route and run again.

Easy way to remember: helpers on top, routes in the middle, errors at the bottom.

Simple lesson: Middleware only helps the routes that come after it.

Writing Custom Middleware

Middleware is just a function. Some common jobs:

  • Header check: reject requests without an API key.
  • Feature flag: turn a route off with one setting.
  • Request property: add req.requestTime for later use.
  • Response header: add X-Canteen to every answer.
  • Normalise data: trim spaces from req.query.search.
  • Response locals: put the user in res.locals.user.

Put each one in its own file in a middleware/ folder, and export it.

  • searchFor is "dosa", without the extra spaces.
  • Every answer carries X-Canteen. Check Headers.

Simple lesson: Small middleware files, each with one job.

Access-Control Middleware

A common job: only let some users through.

  • Read the user's role, for example from a header or the session.
  • Compare it with the role the route needs.
  • Allowed: call next().
  • Not logged in at all: answer 401.
  • Logged in but not allowed: answer 403.

Middleware Factories

Middleware factory = a function that returns middleware.

  • requireRole("staff") returns a checker for staff.
  • requireRole("admin") returns a checker for admins.
  • One function, many configured checkers.

A header like X-Role is easy to fake. This lab uses it only to show the idea. Real apps read the role from a login session. See Cookies and Sessions.

Simple lesson: A factory turns one checker into many configured checkers.

Async Middleware

Middleware can be async when it waits for something, like a database.

  • In Express 5, if an async function throws, Express catches it.
  • The error goes to error middleware, just like next(err).
  • In Express 4, you had to wrap it in try / catch yourself.
  • Errors in callbacks and timers are still not caught. Use next(err) there.
  • /menu/9 throws inside an async function.
  • Express 5 sends the error to the four-input middleware for you.

Simple lesson: In Express 5, throwing in async middleware is safe. Errors reach your error handler.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Forgetting next()Express moves on by itselfYou must call next() or respondThe request hangs
res.send then next()Both can happenAnswer or pass on, not bothERR_HTTP_HEADERS_SENT
Parser after the routesOrder does not matterMiddleware helps only later routesreq.body is undefined
Error middleware with 3 inputsAny middleware can catch errorsIt needs exactly 4 inputsErrors skip it
Error middleware at the topPut important things firstIt must come after routesIt never sees route errors
Trusting a role headerClients send honest headersRead roles from the sessionAnyone becomes admin

Check Yourself

Exercises

What to Remember

  • Middleware is (req, res, next), running in registration order.
  • Finish with next(), a response, or next(err). Never nothing.
  • Mount it globally, on a path, on a router or on one route.
  • Helpers on top, routes in the middle, error middleware at the bottom.
  • Factories like requireRole("staff") make configurable middleware.
  • Express 5 sends errors from async middleware to your error handler.

Next: Body Parsers and Third-Party Middleware. You will read JSON, forms and text from request bodies, and add packages from npm.