What Is Middleware in Express.js: How It Works With Real Code Examples

If you’ve ever tried to build an API with Node.js, you’ve probably bumped into the word middleware and wondered what it actually means. The official docs can feel a bit abstract, so in this guide we’ll skip the dry theory and explain what middleware in Express is using plain language and real, copy-paste-ready code snippets.

By the end of this post, you’ll understand how requests travel through your Express app, how to write your own middleware, and how to handle common tasks like logging, authentication, and errors.

What Is Middleware in Express.js?

Middleware in Express is simply a function that sits between the incoming request and the final response. It has access to three things:

  • req the request object (what the client sent)
  • res the response object (what you’ll send back)
  • next a function that passes control to the next middleware in the chain

Think of middleware as a series of checkpoints. Each request passes through them one by one until something sends back a response.

function myMiddleware(req, res, next) {
  console.log('A request just came in!');
  next(); // pass control to the next middleware
}

That’s it. No magic. Just a function with three parameters.

nodejs code laptop

How the Request-Response Cycle Flows Through Middleware

When a client sends a request to your Express server, it doesn’t jump straight to the route handler. Instead, it walks through a stack of middleware functions in the order you registered them.

Here’s a simplified flow:

  1. Client sends a request (for example, GET /users)
  2. Request enters the first middleware
  3. That middleware does its job, then calls next()
  4. The request moves to the next middleware
  5. Eventually it reaches a route handler that sends a response with res.send() or res.json()

If a middleware doesn’t call next() and doesn’t send a response, the request just hangs. That’s one of the most common beginner bugs.

The 5 Types of Middleware in Express

Type Where It Runs Example Use
Application-level Bound to the app instance Logging, parsing JSON
Router-level Bound to an express.Router() Auth for a group of routes
Built-in Shipped with Express express.json(), express.static()
Third-party Installed via npm cors, helmet, morgan
Error-handling Has 4 arguments: err, req, res, next Catching and formatting errors
nodejs code laptop

Real Code Examples You Can Use Today

1. A Simple Logging Middleware

This logs every request method and URL. Great for debugging.

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

app.use((req, res, next) => {
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  next();
});

app.get('/', (req, res) => {
  res.send('Hello world');
});

app.listen(3000);

Every time a request hits your server, you’ll see a timestamped log line in your terminal.

2. Authentication Check Middleware

Here’s a basic middleware that blocks requests without a valid token.

function requireAuth(req, res, next) {
  const token = req.headers['authorization'];

  if (!token || token !== 'Bearer my-secret-token') {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  // Token is valid, attach user info and continue
  req.user = { id: 42, name: 'Jane' };
  next();
}

app.get('/profile', requireAuth, (req, res) => {
  res.json({ message: `Welcome ${req.user.name}` });
});

Notice how the middleware either sends a response (blocking the request) or calls next() to let it through. That decision is the whole point of middleware.

3. Built-in JSON Body Parser

Before Express can read JSON sent in a POST request, you need to enable the built-in parser.

app.use(express.json());

app.post('/users', (req, res) => {
  console.log(req.body); // now available
  res.status(201).json(req.body);
});

4. Router-Level Middleware

You can apply middleware to a specific group of routes instead of the whole app.

const router = express.Router();

router.use((req, res, next) => {
  console.log('Admin area accessed');
  next();
});

router.get('/dashboard', (req, res) => {
  res.send('Admin dashboard');
});

app.use('/admin', router);

5. Error-Handling Middleware

Error handlers are special: they take four arguments instead of three. Express recognizes them by the signature.

app.get('/crash', (req, res) => {
  throw new Error('Something broke!');
});

// Error-handling middleware - must have 4 args
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: err.message });
});

Always register error-handling middleware last, after all your routes.

The Golden Rules of Middleware

  • Order matters. Middleware runs in the order you call app.use().
  • Always call next() unless you’re sending a response.
  • Never call next() after sending a response or you’ll get a “headers already sent” error.
  • Keep middleware small and focused. One job per function.
  • Error handlers go last and must have four parameters.
nodejs code laptop

Popular Third-Party Middleware Worth Knowing

  • morgan production-ready HTTP request logger
  • cors enables Cross-Origin Resource Sharing
  • helmet sets secure HTTP headers
  • express-rate-limit protects against brute-force attacks
  • cookie-parser parses cookies from request headers

Install any of them with npm install <name> and plug them in with app.use().

Putting It All Together

Here’s a small but complete Express app showing logging, body parsing, auth, a route, and error handling working together:

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

// 1. Logging
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// 2. Body parser
app.use(express.json());

// 3. Auth middleware (used per-route)
function requireAuth(req, res, next) {
  if (req.headers.authorization !== 'Bearer secret') {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

// 4. Routes
app.get('/public', (req, res) => res.send('Open to all'));
app.get('/private', requireAuth, (req, res) => res.send('Top secret'));

// 5. Error handler (always last)
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

app.listen(3000, () => console.log('Server running on :3000'));

Frequently Asked Questions

What is the use of middleware in Express?

Middleware lets you run code between receiving a request and sending a response. It’s used for logging, parsing data, checking authentication, validating input, handling errors, and much more.

What is the difference between middleware and a route handler?

They’re actually similar. A route handler is just the last middleware in the chain that sends the final response. The main difference is that middleware usually calls next() to keep the chain going, while a route handler ends it.

Can I have multiple middleware functions on one route?

Yes. You can chain as many as you want: app.get('/path', mw1, mw2, mw3, handler). They’ll execute in order.

What happens if I forget to call next()?

The request will hang until it times out, because Express doesn’t know whether to move forward or send a response. Always call next() or send a response.

Is middleware only for Express?

No. The middleware pattern exists in many frameworks (Koa, Fastify, ASP.NET, Django), but Express popularized it in the Node.js world. The concept is the same: composable functions running through a pipeline.

How do I write async middleware?

Just declare your function as async and use try/catch to forward errors to next(err), like this:

app.use(async (req, res, next) => {
  try {
    const data = await fetchSomething();
    req.data = data;
    next();
  } catch (err) {
    next(err);
  }
});

Wrapping Up

Middleware is the backbone of every Express application. Once you see it as just a function that takes req, res, and next, the mystery disappears. Start small with a logger, add a body parser, build an auth check, and before you know it you’ll be composing clean, maintainable APIs like a pro.

Now open your editor, spin up an Express app, and try writing your own middleware. The best way to learn it is to break it a few times.