Express.js Middleware

Master Express.js middleware to handle requests, implement authentication, logging, error handling, and build modular applications.

Overview

Middleware functions are the backbone of Express.js applications. They have access to the request object, response object, and the next middleware function in the application's request-response cycle.

What is Middleware?

Middleware functions can execute code, modify request and response objects, end the request-response cycle, or call the next middleware in the stack.

Types of Middleware

Express supports several types of middleware: - **Application-level middleware**: Bound to the app object using app.use() or app.METHOD() - **Router-level middleware**: Works like application-level but is bound to an instance of express.Router() - **Error-handling middleware**: Takes four arguments (err, req, res, next) to handle errors - **Built-in middleware**: Express has built-in middleware like express.static, express.json, and express.urlencoded - **Third-party middleware**: Packages like cors, helmet, morgan for additional functionality

Middleware Execution Order

Middleware functions are executed sequentially in the order they are defined. This order is crucial for proper application behavior.

Creating Custom Middleware

You can create custom middleware for logging, authentication, validation, and more. The key is calling next() to pass control to the next middleware.

Code Examples

Basic Middleware

middleware/logger.js
// Simple logging middleware
const logger = (req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next(); // Pass control to next middleware
};

// Apply to all routes
app.use(logger);

// Apply to specific route
app.use('/api', logger);

Authentication Middleware

middleware/auth.js
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// Protect routes
app.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});

Error Handling Middleware

middleware/errorHandler.js
// Error handling middleware (4 parameters)
const errorHandler = (err, req, res, next) => {
  console.error(err.stack);

  const statusCode = err.statusCode || 500;
  const message = err.message || 'Internal Server Error';

  res.status(statusCode).json({
    error: {
      message,
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
};

// Must be defined after all other middleware and routes
app.use(errorHandler);

Frequently Asked Questions

Why is the order of middleware important?
Middleware executes in the order it's defined. For example, authentication middleware must come before protected routes. Body parsing middleware must come before routes that need to access req.body.
What happens if I forget to call next()?
If you don't call next() and don't send a response, the request will hang indefinitely. Always either send a response or call next() to pass control to the next middleware.
How do I pass data between middleware functions?
You can attach data to the request object (req.user, req.data, etc.). This data will be available in subsequent middleware and route handlers.

Need expert help with Express.js?

Our team at Slashdev.io builds production-ready Express.js applications.