DEV Community

Sospeter Mong'are
Sospeter Mong'are

Posted on

Understanding Middlewares in Express.js and How They Work Internally

In Node.js backend development, Express.js middlewares are the invisible engines quietly shaping every request before it reaches your route handlers. They filter, transform, validate, record, authenticate, or even terminate requests long before your controller functions come into play. Understanding how middlewares actually work under the hood is essential for writing clean, scalable, and maintainable Express applications.

This article breaks down what middlewares are, how Express executes them internally, the different types available, and why they are fundamental to modern API design.


What Exactly Is a Middleware?

In Express.js, a middleware is simply a function shaped like:

(req, res, next) => { ... }
Enter fullscreen mode Exit fullscreen mode

It receives three things:

  • req the incoming request
  • res the outgoing response
  • next() a function that passes control to the next middleware in line

A middleware can:

  • read the request
  • modify the request
  • modify the response
  • call next() to continue the pipeline
  • send a response immediately and stop everything
  • throw an error or call next(err) to trigger error handlers

Middlewares act like checkpoints in a long hallway. Each one decides what happens next.


The Internal Mechanics: How Express Runs Middlewares

Express maintains an internal stack of middleware layers. Each time you use:

app.use(...)
app.get(...)
router.use(...)
Enter fullscreen mode Exit fullscreen mode

Express stores the handler and its associated path in this stack.

When a request arrives:

  1. Express starts at the first item in the stack.
  2. It checks if the request path matches the middleware’s path.
  3. If it matches, Express executes the middleware function.
  4. The middleware runs and must call:
  • next() to continue
  • next(err) to jump to error handlers
  • or send a response to stop the chain

    1. Express moves sequentially through the stack until:
  • a response is sent

  • or there are no more handlers

This is a chain-of-responsibility pattern: each handler gets a turn, in strict order.

Even when using async/await, Express still relies on the next() mechanism for the actual traversal of the stack.


Visualizing the Middleware Flow

Here’s a simple mental model for a request to /groups/123:

Incoming Request
  ↓
authenticateUserStrict
  ↓
checkSubscription
  ↓
checkFeatureAccessAuto
  ↓
trackApiUsage
  ↓
Route Controller (/groups/:id)
  ↓
Response Sent
Enter fullscreen mode Exit fullscreen mode

Each layer is a middleware function. If any one of them decides the request should stop (e.g., invalid API key), it sends a response immediately and the rest are skipped.


Types of Middlewares in Express.js

1. Application-Level Middlewares

Bound directly to the app instance.

app.use((req, res, next) => {
  console.log('Incoming request');
  next();
});
Enter fullscreen mode Exit fullscreen mode

2. Router-Level Middlewares

Scoped to routers.

const router = express.Router();
router.use(authMiddleware);
Enter fullscreen mode Exit fullscreen mode

3. Built-In Middlewares

Provided by Express itself:

  • express.json()
  • express.urlencoded()

4. Third-Party Middlewares

From NPM:

  • cors
  • helmet
  • morgan
  • express-rate-limit

5. Error-Handling Middlewares

Identified by four parameters:

(err, req, res, next) => { ... }
Enter fullscreen mode Exit fullscreen mode

Express jumps to these when next(err) is called.


Why Middlewares Matter

Middlewares give you an elegant way to keep your logic modular and reusable. They are commonly used for:

  • Authentication and authorization
  • API rate limiting
  • Logging and analytics
  • File uploading
  • Body parsing
  • Input validation
  • Role-based access control
  • Feature flagging
  • API usage tracking
  • Caching
  • Response transformation

Instead of stuffing everything into one route handler, you assemble a pipeline that handles each concern gracefully.


How Express Stores Middlewares Internally

If you inspect app._router.stack, you’ll see Express’s internal structure. Each layer looks like:

{
  route: undefined || { path, methods, stack },
  handle: function,
  name: 'middlewareName'
}
Enter fullscreen mode Exit fullscreen mode

This stack is processed top to bottom for every incoming request.


Example: Middleware Pipeline in a Real API

Using your example of a group information endpoint:

app.get('/groups/:id',
  authenticateUserStrict,
  checkSubscription,
  checkFeatureAccessAuto,
  trackApiUsage,
  async (req, res) => {
    // controller logic
  }
);
Enter fullscreen mode Exit fullscreen mode

Express stores these five functions in order.

When a request arrives:

  • authenticateUserStrict runs
  • If everything is OK, next() moves the request to checkSubscription
  • Then checkFeatureAccessAuto
  • Then trackApiUsage
  • Finally, the controller

If any middleware stops the flow, the rest never execute.


Final Thoughts

Middlewares are one of Express’s greatest strengths. They provide structure, clarity, and modularity to your application. Once you understand the internal flow, designing clean APIs becomes much easier.

They turn your app into a smooth-flowing pipeline where each piece has its own job. Whether you’re authenticating users, validating data, or measuring API usage, middlewares give you a predictable and controllable way to do it.

Top comments (0)