Skip to main content

Express Custom Middleware

In Express.js applications, middleware functions play a crucial role in handling HTTP requests and responses. While Express provides several built-in middleware functions, creating your own custom middleware allows you to add specific functionality tailored to your application's needs.

What is Custom Middleware?

Custom middleware in Express is simply a function that you write yourself to perform operations on the request or response objects, or to execute code during the request-response cycle. These functions have access to:

  • The request object (req)
  • The response object (res)
  • The next middleware function in the application's request-response cycle (next)
  • Any errors that might have occurred (err)

Basic Structure of Custom Middleware

A custom middleware function follows this basic structure:

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

// Call next() to pass control to the next middleware
next();
}

Creating Your First Custom Middleware

Let's start by creating a simple logging middleware that logs the time and method of each request:

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

// Custom logging middleware
const requestLogger = (req, res, next) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] ${req.method} request to ${req.url}`);
next(); // Don't forget to call next() to continue to the next middleware!
};

// Use the custom middleware
app.use(requestLogger);

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

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

When you make a request to this server, you'll see output like:

[2023-04-15T12:34:56.789Z] GET request to /

The next() Function

The next() function is a crucial part of middleware. When called, it passes control to the next middleware function in the stack. If you forget to call next(), the request will hang and never reach the subsequent middleware or route handlers!

There are three ways to use next():

  1. next() - Pass control to the next middleware
  2. next('route') - Skip remaining middleware in this route and go to the next route
  3. next(error) - Pass an error to Express error handlers

Middleware Application Scopes

You can apply middleware at different levels:

Application-level Middleware

This applies to your entire application:

javascript
// Applies to all routes
app.use(requestLogger);

Route-level Middleware

This applies to specific routes:

javascript
// Applies only to this route
app.get('/admin', authMiddleware, (req, res) => {
res.send('Admin Dashboard');
});

Router-level Middleware

This applies to a specific router instance:

javascript
const router = express.Router();
router.use(requestLogger);

Practical Example: Authentication Middleware

Let's create a more useful middleware that checks if a user is authenticated:

javascript
// Authentication middleware
const checkAuth = (req, res, next) => {
// Check for auth token in headers
const authToken = req.headers.authorization;

if (!authToken || !authToken.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Authentication required' });
}

// Extract and verify token (simplified example)
const token = authToken.split(' ')[1];

if (token === 'valid-token') {
// Add user info to request object for use in route handlers
req.user = { id: 123, username: 'exampleuser' };
next();
} else {
res.status(401).json({ message: 'Invalid token' });
}
};

// Protected route
app.get('/profile', checkAuth, (req, res) => {
res.json({
message: 'Profile accessed successfully',
user: req.user
});
});

Error-Handling Middleware

Error-handling middleware has a special signature with four parameters instead of three:

javascript
const errorHandler = (err, req, res, next) => {
console.error(`Error: ${err.message}`);

// Send error response
res.status(500).json({
error: {
message: 'An error occurred on the server',
details: process.env.NODE_ENV === 'development' ? err.message : null
}
});
};

// This should be the last middleware added
app.use(errorHandler);

To trigger the error handler from another middleware or route handler:

javascript
app.get('/problematic', (req, res, next) => {
try {
// Something that might fail
const result = someFunction();
res.json({ result });
} catch (error) {
next(error); // Pass to error handler
}
});

Real-World Example: Request Processing Pipeline

Let's build a complete middleware pipeline for a typical API endpoint:

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

// Middleware to parse JSON body
app.use(express.json());

// Request logger middleware
const logger = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
const start = Date.now();

// When response ends, log the duration
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`[${new Date().toISOString()}] Completed ${res.statusCode} in ${duration}ms`);
});

next();
};

// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ message: 'Authentication required' });
}
// Simplified auth check
req.user = { id: 42, role: 'user' };
next();
};

// Rate limiting middleware
const rateLimit = (req, res, next) => {
// Using user ID from authentication middleware
const userId = req.user?.id || 'anonymous';

// Simple in-memory rate limiting (not for production)
const requestCounts = {};

if (!requestCounts[userId]) {
requestCounts[userId] = 1;
} else if (requestCounts[userId] > 100) {
return res.status(429).json({ message: 'Too many requests' });
} else {
requestCounts[userId]++;
}

next();
};

// Apply middleware
app.use(logger);

// Protected routes with multiple middleware
app.get('/api/data', authenticate, rateLimit, (req, res) => {
res.json({
message: 'Data retrieved successfully',
data: { items: [1, 2, 3] },
user: req.user
});
});

// Error handling middleware
app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).json({ message: 'Server error' });
});

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

Creating Configurable Middleware

Make your middleware more flexible by creating a factory function:

javascript
// Configurable timeout middleware
const timeout = (limit) => {
return (req, res, next) => {
const timeoutId = setTimeout(() => {
res.status(408).json({ message: 'Request timeout' });
}, limit);

// Clear the timeout when the response is sent
res.on('finish', () => {
clearTimeout(timeoutId);
});

next();
};
};

// Use with different timeouts for different routes
app.get('/fast', timeout(1000), (req, res) => {
res.send('Fast route');
});

app.get('/slow', timeout(5000), (req, res) => {
res.send('Slow route');
});

Summary

Custom middleware is a powerful feature of Express that allows you to:

  • Add functionality to the request-response cycle
  • Create reusable components for common tasks
  • Structure your application's logic in a modular way
  • Handle cross-cutting concerns like logging, authentication, and error handling

Remember these key points when creating middleware:

  1. Always call next() (unless you're deliberately ending the response)
  2. Keep middleware functions focused on a single responsibility
  3. Use middleware application scopes appropriately
  4. Place error-handling middleware last in your application

Additional Resources

Exercises

  1. Create a middleware that tracks the response time for each request and adds it as a header to the response.
  2. Build an authorization middleware that checks if a user has the correct role to access certain routes.
  3. Implement a middleware that validates input data based on a schema before it reaches your route handlers.
  4. Create a caching middleware that stores responses for GET requests and returns the cached response when the same URL is requested again.
  5. Build a middleware pipeline for a file upload endpoint with authentication, file size validation, and file type checking.


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