Skip to main content

Express Middleware Introduction

What is Middleware?

Middleware functions are the backbone of Express.js applications. They are functions that have access to the request object (req), the response object (res), and the next middleware function in the application's request-response cycle. These functions can:

  • Execute any code
  • Modify the request and response objects
  • End the request-response cycle
  • Call the next middleware in the stack

Think of middleware as a series of processing layers that your requests go through before getting a response. Each layer can do something with the request, then either send a response or pass control to the next middleware.

Basic Middleware Structure

A middleware function follows this basic pattern:

javascript
function myMiddleware(req, res, next) {
// Do something with req or res

// Then call next() to pass control to the next middleware
next();
}

The next parameter is a function that, when called, executes the next middleware in the stack. If you don't call next(), the request will be left hanging!

How Middleware Works in Express

When a request comes into your Express application, it passes through each middleware function in the order they are defined. This creates a "middleware stack" or pipeline:

Client Request → Middleware 1 → Middleware 2 → Route Handler → Response

Let's see this in action with a simple example:

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

// Middleware 1: Logger
app.use((req, res, next) => {
console.log(`${new Date().toISOString()}: ${req.method} ${req.url}`);
next(); // Pass control to the next middleware
});

// Middleware 2: Request timer
app.use((req, res, next) => {
req.requestTime = Date.now();
next();
});

// Route handler
app.get('/', (req, res) => {
res.send(`Hello! Your request came in at: ${new Date(req.requestTime).toISOString()}`);
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

When a request comes to the root route /, it will:

  1. First go through the logger middleware (printing the request info)
  2. Then through the timer middleware (adding a timestamp to the request)
  3. Finally reach the route handler (which uses the timestamp to respond)

Adding Middleware to Your Application

There are three ways to use middleware in Express:

1. Application-level middleware

Applied using app.use() or app.METHOD() (where METHOD is get, post, etc.):

javascript
// This middleware applies to all routes
app.use((req, res, next) => {
console.log('This middleware runs for every request');
next();
});

// This middleware only applies to the specified path
app.use('/user', (req, res, next) => {
console.log('This runs only for routes starting with /user');
next();
});

2. Router-level middleware

Similar to application middleware but bound to an instance of express.Router():

javascript
const express = require('express');
const router = express.Router();

router.use((req, res, next) => {
console.log('Router specific middleware');
next();
});

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

// Then use the router in your app
const app = express();
app.use('/user', router);

3. Error-handling middleware

Special middleware that takes four parameters instead of three:

javascript
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});

Creating Your First Custom Middleware

Let's create a simple authentication middleware:

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

// Authentication middleware
function authMiddleware(req, res, next) {
// Get auth token from header
const authToken = req.headers.authorization;

if (authToken === 'secret-token-123') {
// If authentication is successful
req.user = { id: 1, name: 'Example User' };
next(); // Proceed to the next middleware/route handler
} else {
// If authentication fails
res.status(401).send('Unauthorized: Invalid token');
// Note: We don't call next() here because we're ending the request-response cycle
}
}

// Use the middleware only for protected routes
app.get('/public', (req, res) => {
res.send('This is public content');
});

app.get('/protected', authMiddleware, (req, res) => {
res.send(`Hello, ${req.user.name}! This is protected content.`);
});

app.listen(3000, () => {
console.log('Server is running on port 3000');
});

Input/Output Example

If you test this with different requests:

Public Route (No Auth Required):

GET /public

Output:

This is public content

Protected Route (Without Auth):

GET /protected

Output:

Unauthorized: Invalid token

Protected Route (With Auth):

GET /protected
Headers: Authorization: secret-token-123

Output:

Hello, Example User! This is protected content.

Practical Use Case: Request Logger

Here's a practical middleware that logs details about incoming requests:

javascript
const fs = require('fs');
const path = require('path');

function requestLogger(req, res, next) {
const start = Date.now();

// Process the request
next();

// After the request is processed (this runs after the route handler)
const duration = Date.now() - start;
const log = `${new Date().toISOString()} | ${req.method} ${req.url} | ${res.statusCode} | ${duration}ms\n`;

// Append to log file
fs.appendFile(
path.join(__dirname, 'requests.log'),
log,
(err) => {
if (err) console.error('Failed to log request:', err);
}
);
}

// In your Express app
const app = express();
app.use(requestLogger);

This middleware:

  1. Records when the request started
  2. Calls next() to process the request
  3. After the route handler completes, calculates the duration
  4. Logs the request details to a file

Common Built-in and Third-Party Middleware

Express has several built-in middleware functions and there are many popular third-party options:

Built-in middleware:

javascript
// Parse JSON bodies
app.use(express.json());

// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));

// Serve static files
app.use(express.static('public'));
javascript
// Session management
const session = require('express-session');
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));

// HTTP request logging
const morgan = require('morgan');
app.use(morgan('dev'));

// CORS support
const cors = require('cors');
app.use(cors());

Middleware Execution Order

The order in which you define middleware is crucial. Middleware functions are executed sequentially:

javascript
// This runs first
app.use((req, res, next) => {
console.log('Middleware 1');
next();
});

// This runs second
app.use((req, res, next) => {
console.log('Middleware 2');
next();
});

// This route handler runs after both middleware
app.get('/', (req, res) => {
res.send('Hello World!');
});

If a middleware doesn't call next(), all subsequent middleware and route handlers won't be executed.

Summary

Express middleware is the foundation of Express.js applications. Through middleware:

  • You can process requests before they reach route handlers
  • You can modify request and response objects
  • You can end the request-response cycle or pass control to the next middleware
  • You can implement cross-cutting concerns like logging, authentication, and error handling

Understanding middleware is essential for building robust, maintainable Express applications.

Additional Resources

Exercises

  1. Create a middleware that adds a timestamp to every request object.
  2. Build a rate-limiting middleware that allows only 10 requests per minute from each IP address.
  3. Create a middleware chain with at least three custom middleware functions and observe the order of execution.
  4. Add error-handling middleware to an Express application that catches errors and responds with appropriate status codes.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)