Middleware

Understand and create middleware for request processing, authentication, and more

What is Middleware?

Middleware functions are functions that have access to the request object (req), response object (res), and the next middleware function in the application's request-response cycle. They can execute code, modify request/response, end the request-response cycle, or call the next middleware.

🔗 Middleware Flow

Request → Middleware 1 → Middleware 2 → Route Handler → Response
                ↓              ↓
           next()         next()

// Each middleware must either:
// 1. Call next() to pass to next middleware
// 2. Send a response to end the cycle

Basic Middleware

import express, { Request, Response, NextFunction } from 'express';

const app = express();

// Simple logging middleware
const logger = (req: Request, res: Response, next: NextFunction) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next(); // Pass to next middleware
};

// Apply globally
app.use(logger);

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

// Apply to specific route
app.get('/users', logger, (req, res) => {
  res.json([]);
});

// Multiple middleware on a route
app.get('/protected', 
  authenticate,
  authorize('admin'),
  (req, res) => {
    res.json({ secret: 'data' });
  }
);

Built-in Middleware

import express from 'express';
const app = express();

// Parse JSON bodies
app.use(express.json());
// Configurable
app.use(express.json({ limit: '10mb' }));

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

// Serve static files
app.use(express.static('public'));
// With virtual path prefix
app.use('/static', express.static('public'));
// Multiple directories
app.use(express.static('public'));
app.use(express.static('uploads'));

// Raw body (for webhooks)
app.use('/webhooks', express.raw({ type: 'application/json' }));

// Text body
app.use(express.text());

Popular Third-Party Middleware

// Install
npm install cors helmet morgan compression cookie-parser express-rate-limit

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import compression from 'compression';
import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit';

const app = express();

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

// CORS - Cross-Origin Resource Sharing
app.use(cors());
// Configured CORS
app.use(cors({
  origin: ['http://localhost:3000', 'https://myapp.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  credentials: true
}));

// HTTP request logging
app.use(morgan('dev'));       // Colored dev format
app.use(morgan('combined'));  // Apache combined format

// Gzip compression
app.use(compression());

// Parse cookies
app.use(cookieParser());
app.use(cookieParser('secret')); // Signed cookies

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per window
  message: { error: 'Too many requests' }
});
app.use('/api', limiter);

📦 Express Middleware List →

Custom Middleware Examples

// Request timing middleware
const timing = (req: Request, res: Response, next: NextFunction) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} - ${duration}ms`);
  });
  
  next();
};

// Request ID middleware
import { v4 as uuidv4 } from 'uuid';

const requestId = (req: Request, res: Response, next: NextFunction) => {
  req.id = req.headers['x-request-id'] as string || uuidv4();
  res.setHeader('X-Request-Id', req.id);
  next();
};

// Async middleware wrapper
const asyncHandler = (fn: Function) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Usage
app.get('/users', asyncHandler(async (req, res) => {
  const users = await User.findAll(); // No try/catch needed
  res.json(users);
}));

// Validation middleware
const validateBody = (schema: any) => {
  return (req: Request, res: Response, next: NextFunction) => {
    const { error } = schema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.details[0].message });
    }
    next();
  };
};

Authentication Middleware

import jwt from 'jsonwebtoken';

interface AuthRequest extends Request {
  user?: { id: string; email: string; role: string };
}

// JWT Authentication
const authenticate = (req: AuthRequest, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = decoded as AuthRequest['user'];
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// Authorization middleware factory
const authorize = (...roles: string[]) => {
  return (req: AuthRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
};

// Usage
app.get('/admin/dashboard', 
  authenticate, 
  authorize('admin', 'superadmin'),
  (req, res) => {
    res.json({ message: 'Welcome, admin!' });
  }
);

Middleware Order Matters!

const app = express();

// ✅ CORRECT ORDER

// 1. Security first
app.use(helmet());
app.use(cors());

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

// 3. Logging
app.use(morgan('dev'));

// 4. Compression
app.use(compression());

// 5. Rate limiting
app.use('/api', limiter);

// 6. Static files (if needed)
app.use(express.static('public'));

// 7. Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);

// 8. 404 handler
app.use((req, res) => {
  res.status(404).json({ error: 'Not found' });
});

// 9. Error handler (MUST BE LAST)
app.use(errorHandler);

💡 Middleware Best Practices

  • • Always call next() unless you're ending the response
  • • Keep middleware focused - one responsibility per middleware
  • • Order matters - security, parsing, logging, then routes
  • • Use app.use() for global, router.use() for scoped
  • • Wrap async middleware to catch errors properly
  • • Error-handling middleware has 4 parameters: (err, req, res, next)