Skip to main content

Express Application Middleware

Introduction

Application-level middleware in Express.js is one of the most powerful features that makes the framework flexible and extensible. When you apply middleware at the application level, it executes for every request that comes to your Express app. This gives you the ability to perform operations like authentication, logging, or data parsing across your entire application without having to add the same code to each route handler.

In this guide, we'll explore:

  • What application middleware is
  • How to implement it in Express applications
  • Common use cases and best practices
  • Real-world examples of application middleware

Understanding Application Middleware

Application middleware functions 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
  • Make changes to the request and response objects
  • End the request-response cycle
  • Call the next middleware function in the stack

Middleware Execution Flow

When a request comes to your Express application, it passes through all application middleware in the order they were defined before reaching the route handlers.

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

Implementing Application Middleware

Basic Syntax

Here's the basic syntax for defining application-level middleware:

javascript
app.use([path,] callback [, callback...])
  • path (optional): If specified, the middleware will only be executed for requests whose path begins with this path
  • callback: The middleware function(s) to execute

Example: Simple Logging Middleware

Let's create a simple middleware function that logs information about each incoming request:

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

// Application middleware that logs request details
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // Don't forget to call next() to pass control to the next middleware
});

// Routes defined after the middleware
app.get('/', (req, res) => {
res.send('Hello World!');
});

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

When you send requests to this server, you'll see log entries for each request:

[2023-07-25T15:42:31.243Z] GET /
[2023-07-25T15:42:34.986Z] GET /about
[2023-07-25T15:42:38.510Z] POST /api/data

The next() Function

The next() function is crucial in middleware. It tells Express to move on to the next middleware in the stack. If you don't call next(), the request will be left hanging and the client won't receive a response.

Error Handling with next()

You can also use next() to pass errors to Express's error handling mechanism:

javascript
app.use((req, res, next) => {
if (!req.headers.authorization) {
// Pass an error to Express's error handlers
next(new Error('Authentication required'));
} else {
next(); // Continue to next middleware
}
});

// Error handling middleware (always has 4 parameters)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something went wrong!');
});

Common Application Middleware Use Cases

1. Request Body Parsing

Express doesn't parse request bodies by default. You need middleware like express.json() or express.urlencoded() to handle this:

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

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

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

app.post('/api/users', (req, res) => {
console.log(req.body); // Now req.body contains the parsed request body
res.send('User created');
});

2. Serving Static Files

The express.static middleware makes it easy to serve static files:

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

// Serve static files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

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

Now, files in the public directory can be accessed directly:

  • public/styles.css is available at http://localhost:3000/styles.css
  • public/images/logo.png is available at http://localhost:3000/images/logo.png

3. CORS (Cross-Origin Resource Sharing)

To handle CORS in an Express app, you can use the cors middleware:

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

// Enable CORS for all routes
app.use(cors());

// Or with options
app.use(cors({
origin: 'https://yourdomain.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
}));

app.get('/api/data', (req, res) => {
res.json({ message: 'This response can be accessed from other domains' });
});

4. Authentication

Here's a simplified authentication middleware:

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

// Authentication middleware
app.use((req, res, next) => {
const apiKey = req.header('X-API-Key');

if (!apiKey || apiKey !== 'your-secret-api-key') {
return res.status(401).json({ error: 'Unauthorized' });
}

// If authentication successful, attach user info to request
req.user = { id: 123, name: 'Example User' };
next();
});

app.get('/protected-route', (req, res) => {
res.json({ message: `Hello, ${req.user.name}!` });
});

Path-Specific Application Middleware

You can also apply middleware to specific paths only:

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

// This middleware only runs for requests to /api/*
app.use('/api', (req, res, next) => {
console.log('API request received');
next();
});

app.get('/api/users', (req, res) => {
res.send('Users API');
});

app.get('/other-path', (req, res) => {
// The middleware won't run for this route
res.send('Other path');
});

Multiple Middleware Functions

You can define multiple middleware functions for the same path:

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

app.use('/api',
(req, res, next) => {
console.log('First middleware');
next();
},
(req, res, next) => {
console.log('Second middleware');
next();
}
);

// Or chain multiple use() calls
app.use((req, res, next) => {
req.requestTime = Date.now();
next();
});

app.use((req, res, next) => {
console.log(`Request started: ${req.requestTime}`);
next();
});

Real-World Example: Complete Application with Middleware

Let's put everything together in a more complete example of a small API with multiple middleware functions:

javascript
const express = require('express');
const morgan = require('morgan');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const app = express();

// Security middleware
app.use(helmet());

// Logging middleware
app.use(morgan('combined'));

// Rate limiting middleware
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);

// Request body parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Custom middleware to add request ID
app.use((req, res, next) => {
req.requestId = Date.now().toString(36) + Math.random().toString(36).substr(2);
next();
});

// Route-specific middleware
app.use('/api', (req, res, next) => {
console.log(`API Request ${req.requestId}: ${req.method} ${req.path}`);
next();
});

// Routes
app.get('/', (req, res) => {
res.send('Welcome to our API!');
});

app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]);
});

// Error handling middleware (must be defined last)
app.use((err, req, res, next) => {
console.error(`Error ${req.requestId}:`, err.stack);
res.status(500).json({
error: 'Something went wrong',
requestId: req.requestId
});
});

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

In this example:

  1. We use several popular middleware packages (helmet, morgan, express-rate-limit)
  2. We configure built-in Express middleware for parsing request bodies
  3. We create custom application middleware to add a unique request ID
  4. We define route-specific middleware for the /api path
  5. We set up error handling middleware that logs errors with the request ID

Best Practices

  1. Order matters: Middleware executes in the order defined. Place important middleware like authentication early in the stack.

  2. Always call next(): Unless you're ending the request-response cycle with res.send(), res.json(), etc., always call next().

  3. Error handling: Place error handling middleware at the end of your middleware stack.

  4. Keep middleware focused: Each middleware should perform a single, well-defined task.

  5. Use third-party middleware: For common tasks, use established middleware packages instead of writing your own.

  6. Consider performance: Remember that every request will go through your application middleware, so keep them efficient.

Summary

Application middleware in Express is a powerful way to add functionality that applies across your entire application. Some key takeaways:

  • Middleware functions have access to the request object, response object, and next middleware function
  • They can process requests, modify request/response objects, and either pass control to the next middleware or end the request-response cycle
  • Common use cases include logging, authentication, parsing request bodies, and serving static files
  • You can apply middleware to specific paths or to the entire application
  • The order of middleware definition matters, as they execute in sequence

By mastering application middleware, you can build modular, maintainable Express applications with clean separation of concerns.

Additional Resources

Exercises

  1. Create a simple Express application with a middleware that adds a X-Response-Time header showing how long the request took to process.

  2. Build a middleware that checks if a user is authenticated and redirects to a login page if not.

  3. Implement a middleware that logs all incoming requests to a file instead of the console.

  4. Create a rate-limiting middleware from scratch (without using a library) that limits users to 10 requests per minute.

  5. Build an Express application that combines at least three different middleware functions to process requests.



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