Lesson 10 of 20

Middleware in Express

Built-in and Custom Middleware

Express comes with several built-in middleware functions and supports an enormous ecosystem of third-party middleware. You can also write your own middleware for logging, authentication, validation, and more.

Middleware functions receive three arguments: req, res, and next. They can modify the request or response objects, end the cycle by sending a response, or call next() to pass control to the next middleware.

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

// --- Built-in Middleware ---
app.use(express.json());                 // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse form data
app.use(express.static('public'));       // Serve static files

// --- Custom Middleware: Request Logger ---
function logger(req, res, next) {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
  });
  next();
}
app.use(logger);

// --- Custom Middleware: Auth Check ---
function requireAuth(req, res, next) {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  // Verify token here...
  next();
}

// Apply to specific routes only
app.get('/api/profile', requireAuth, (req, res) => {
  res.json({ user: 'Alice' });
});
  • express.json() — Parse incoming JSON request bodies
  • express.urlencoded() — Parse URL-encoded form data
  • express.static() — Serve static files from a directory
  • app.use(fn) — Apply middleware to all routes
  • app.get('/path', middleware, handler) — Apply middleware to specific routes
Notes
  • Middleware executes in the order it is defined. Place global middleware (like logger, body parser) before your routes.

Error Handling Middleware

Error handling middleware is special — it takes four arguments (err, req, res, next). Express recognizes it by the four parameters. When an error is thrown or passed to next(err), Express skips to the error handler.

Always define error handling middleware last, after all routes. This catches errors from any route or middleware above it.

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

app.use(express.json());

// Route that might throw an error
app.get('/api/users/:id', (req, res, next) => {
  try {
    const id = parseInt(req.params.id);
    if (isNaN(id)) {
      const error = new Error('Invalid user ID');
      error.statusCode = 400;
      throw error;
    }
    res.json({ id, name: 'Alice' });
  } catch (err) {
    next(err); // Pass error to error handler
  }
});

// 404 handler — must be after all routes
app.use((req, res, next) => {
  res.status(404).json({ error: 'Route not found' });
});

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

app.listen(3000);
Notes
  • Error middleware MUST have exactly 4 parameters (err, req, res, next) — even if you do not use next. Express uses the parameter count to identify it as an error handler.

Third-Party Middleware

The Express ecosystem has hundreds of useful middleware packages. Common ones include morgan for logging, cors for cross-origin requests, and helmet for security headers.

Install them with npm and use them like any other middleware.

Example
// npm install morgan cors helmet

const express = require('express');
const morgan = require('morgan');   // HTTP request logger
const cors = require('cors');       // Cross-Origin Resource Sharing
const helmet = require('helmet');   // Security headers

const app = express();

// Apply third-party middleware
app.use(helmet());              // Set security headers
app.use(cors());                // Allow cross-origin requests
app.use(morgan('dev'));         // Log requests: GET /api/users 200 5ms
app.use(express.json());

// CORS with options
app.use(cors({
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}));

app.get('/api/data', (req, res) => {
  res.json({ message: 'Secured and logged!' });
});

app.listen(3000);
Notes
  • helmet() sets various HTTP headers to protect your app from common web vulnerabilities like XSS, clickjacking, and MIME sniffing.