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:
app.use([path,] callback [, callback...])
path
(optional): If specified, the middleware will only be executed for requests whose path begins with this pathcallback
: The middleware function(s) to execute
Example: Simple Logging Middleware
Let's create a simple middleware function that logs information about each incoming request:
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:
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:
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:
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 athttp://localhost:3000/styles.css
public/images/logo.png
is available athttp://localhost:3000/images/logo.png
3. CORS (Cross-Origin Resource Sharing)
To handle CORS in an Express app, you can use the cors
middleware:
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:
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:
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:
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:
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:
- We use several popular middleware packages (
helmet
,morgan
,express-rate-limit
) - We configure built-in Express middleware for parsing request bodies
- We create custom application middleware to add a unique request ID
- We define route-specific middleware for the
/api
path - We set up error handling middleware that logs errors with the request ID
Best Practices
-
Order matters: Middleware executes in the order defined. Place important middleware like authentication early in the stack.
-
Always call
next()
: Unless you're ending the request-response cycle withres.send()
,res.json()
, etc., always callnext()
. -
Error handling: Place error handling middleware at the end of your middleware stack.
-
Keep middleware focused: Each middleware should perform a single, well-defined task.
-
Use third-party middleware: For common tasks, use established middleware packages instead of writing your own.
-
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
- Express.js Official Middleware Documentation
- Popular Express middleware packages
- Writing custom middleware for Express.js
Exercises
-
Create a simple Express application with a middleware that adds a
X-Response-Time
header showing how long the request took to process. -
Build a middleware that checks if a user is authenticated and redirects to a login page if not.
-
Implement a middleware that logs all incoming requests to a file instead of the console.
-
Create a rate-limiting middleware from scratch (without using a library) that limits users to 10 requests per minute.
-
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! :)