Menu

HTML & JS Topic

Node.js Middleware in Depth

Middleware Execution Flow

Understanding the Middleware Pipeline:

Middleware functions are executed in the order they are defined, forming a pipeline.

javascript

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

// Visualizing the middleware pipeline
app.use((req, res, next) => {
  console.log('1. First middleware - Start');
  req.startTime = Date.now();
  next(); // Pass to next middleware
});

app.use((req, res, next) => {
  console.log('2. Second middleware');
  req.user = { id: 1, name: 'Test User' };
  next();
});

app.get('/test', (req, res, next) => {
  console.log('3. Route handler');
  const duration = Date.now() - req.startTime;
  res.json({
    message: 'Request completed',
    user: req.user,
    duration: `${duration}ms`
  });
});

app.use((req, res, next) => {
  console.log('4. This runs AFTER route handler sends response');
  // This won't run because response was already sent
});

app.listen(3000);

Middleware Chain Visualization:

text

Request → MW1 → MW2 → MW3 → Route Handler → Response
         (next)→(next)→(next)→(res.send)→(sent to client)

Important Rules:

  1. Order matters - Middleware executes in registration order

  2. next() passes control to next middleware

  3. res.send()/res.json()/res.end() ends the chain

  4. Middleware after response is sent won't execute

Built-in Middleware

1. express.json() - Parse JSON bodies

javascript

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

// Parse application/json
app.use(express.json());

// Options
app.use(express.json({
  limit: '10mb',            // Maximum request body size
  strict: true,             // Only accept arrays and objects
  inflate: true,            // Handle deflated bodies
  type: 'application/json', // Content-Type to parse
  verify: (req, res, buf, encoding) => {
    // Custom verification
    try {
      JSON.parse(buf);
    } catch(e) {
      throw new Error('Invalid JSON');
    }
  }
}));

// Example usage
app.post('/api/users', (req, res) => {
  // req.body is now parsed JSON
  console.log('Request body:', req.body);
  
  if (!req.body.name) {
    return res.status(400).json({ error: 'Name is required' });
  }
  
  res.json({ message: 'User created', data: req.body });
});

2. express.urlencoded() - Parse URL-encoded bodies

javascript

// Parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// Extended: false (default) uses querystring library
// Extended: true uses qs library (supports nested objects)

app.post('/form', (req, res) => {
  // For form data: name=John&email=john@example.com
  console.log('Form data:', req.body); // { name: 'John', email: 'john@example.com' }
  res.send('Form submitted');
});

// With options
app.use(express.urlencoded({
  extended: true,
  limit: '10mb',
  parameterLimit: 1000, // Max number of parameters
  type: 'application/x-www-form-urlencoded'
}));

3. express.static() - Serve static files

javascript

const path = require('path');

// Serve static files from 'public' directory
app.use(express.static('public'));

// Multiple static directories
app.use(express.static('public'));
app.use(express.static('uploads'));

// Virtual path prefix
app.use('/static', express.static('public'));
// Now files are available at /static/css/style.css

// With options
app.use(express.static('public', {
  dotfiles: 'ignore', // How to handle dotfiles: 'allow', 'deny', 'ignore'
  etag: true, // Enable or disable etag generation
  extensions: ['html', 'htm'], // File extensions to try
  fallthrough: true, // Let requests fall through to other middleware
  immutable: false, // Enable or disable immutable directive in Cache-Control
  index: 'index.html', // Default file to serve
  lastModified: true, // Set Last-Modified header
  maxAge: 3600000, // Cache control max-age in milliseconds
  redirect: true, // Redirect to trailing "/" when pathname is dir
  setHeaders: (res, path, stat) => {
    // Custom headers
    res.set('x-timestamp', Date.now());
    if (path.endsWith('.pdf')) {
      res.set('Content-Disposition', 'attachment');
    }
  }
}));

// Example file structure:
// public/
//   ├── index.html
//   ├── css/
//   │   └── style.css
//   └── js/
//       └── app.js

// Accessible at:
// GET / → serves public/index.html
// GET /css/style.css → serves public/css/style.css

4. express.Router() - Modular route handlers

javascript

// Create router instance
const router = express.Router();

// Router-level middleware
router.use((req, res, next) => {
  console.log('Router middleware - Time:', Date.now());
  next();
});

// Define routes
router.get('/', (req, res) => {
  res.send('Router home');
});

router.get('/about', (req, res) => {
  res.send('About page');
});

// Nested routers
const userRouter = express.Router();
userRouter.get('/profile', (req, res) => {
  res.send('User profile');
});

router.use('/user', userRouter);

// Mount router
app.use('/api', router);
// Routes become: /api/, /api/about, /api/user/profile

5. express.text() - Parse text bodies

javascript

// Parse text/plain
app.use(express.text());

app.post('/text', (req, res) => {
  console.log('Text body:', req.body); // String
  res.send('Text received');
});

app.use(express.text({
  type: 'text/plain', // Content-Type to parse
  defaultCharset: 'utf-8' // Default charset
}));

6. express.raw() - Parse raw/buffer bodies

javascript

// Parse application/octet-stream
app.use(express.raw({
  type: 'application/octet-stream',
  limit: '10mb'
}));

app.post('/upload-binary', (req, res) => {
  console.log('Raw buffer:', req.body); // Buffer object
  // Process binary data
  res.send('Binary data received');
});

Custom Middleware

1. Logger Middleware

javascript

// Custom logger middleware
const logger = (format = 'combined') => {
  return (req, res, next) => {
    const start = Date.now();
    
    // Store original end method
    const originalEnd = res.end;
    
    // Override end method to log after response
    res.end = function(...args) {
      const duration = Date.now() - start;
      const { method, url, ip } = req;
      const { statusCode } = res;
      const contentLength = res.get('Content-Length') || 0;
      
      let logMessage;
      switch(format) {
        case 'short':
          logMessage = `${method} ${url} ${statusCode} ${duration}ms`;
          break;
        case 'dev':
          logMessage = `${method} ${url} ${statusCode} ${duration}ms - ${contentLength}`;
          break;
        default: // combined
          const timestamp = new Date().toISOString();
          const userAgent = req.get('User-Agent') || '';
          logMessage = `${timestamp} ${ip} ${method} ${url} ${statusCode} ${duration}ms ${contentLength} "${userAgent}"`;
      }
      
      console.log(logMessage);
      
      // Call original end method
      originalEnd.apply(res, args);
    };
    
    next();
  };
};

// Usage
app.use(logger('combined'));
app.use(logger('dev'));
app.use(logger('short'));

2. Authentication Middleware

javascript

// JWT Authentication middleware
const jwt = require('jsonwebtoken');

const authenticate = (options = {}) => {
  return (req, res, next) => {
    // Get token from header, query, or cookie
    const token = 
      req.headers.authorization?.split(' ')[1] || // Bearer token
      req.query.token ||
      req.cookies?.token;
    
    if (!token) {
      if (options.public) {
        return next(); // Allow public access
      }
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    try {
      // Verify token
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      
      // Attach user to request
      req.user = decoded;
      
      // Check roles if specified
      if (options.roles && !options.roles.includes(decoded.role)) {
        return res.status(403).json({ error: 'Insufficient permissions' });
      }
      
      next();
    } catch (error) {
      res.status(401).json({ error: 'Invalid token' });
    }
  };
};

// Usage with different options
app.get('/public', authenticate({ public: true }), (req, res) => {
  res.json({ message: 'Public route' });
});

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

app.get('/admin', authenticate({ roles: ['admin'] }), (req, res) => {
  res.json({ message: 'Admin access granted' });
});

3. Validation Middleware

javascript

const { body, query, param, validationResult } = require('express-validator');

// Validation middleware factory
const validate = (validations) => {
  return async (req, res, next) => {
    // Run all validations
    await Promise.all(validations.map(validation => validation.run(req)));
    
    // Check for errors
    const errors = validationResult(req);
    if (errors.isEmpty()) {
      return next();
    }
    
    // Format errors
    const formattedErrors = errors.array().map(err => ({
      field: err.param,
      message: err.msg,
      value: err.value,
      location: err.location
    }));
    
    res.status(400).json({
      error: 'Validation failed',
      details: formattedErrors
    });
  };
};

// Usage
app.post('/register', 
  validate([
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 6 }),
    body('confirmPassword').custom((value, { req }) => {
      if (value !== req.body.password) {
        throw new Error('Passwords do not match');
      }
      return true;
    }),
    body('age').optional().isInt({ min: 18, max: 100 })
  ]),
  (req, res) => {
    // If we get here, validation passed
    res.json({ message: 'Registration successful' });
  }
);

app.get('/users/:id',
  validate([
    param('id').isInt().withMessage('ID must be an integer'),
    query('page').optional().isInt({ min: 1 })
  ]),
  (req, res) => {
    res.json({ userId: req.params.id });
  }
);

4. Rate Limiting Middleware

javascript

const rateLimit = (options = {}) => {
  const {
    windowMs = 15 * 60 * 1000, // 15 minutes
    max = 100, // Limit each IP to 100 requests per windowMs
    message = 'Too many requests, please try again later.',
    skip = () => false // Function to skip rate limiting
  } = options;
  
  const requests = new Map();
  
  setInterval(() => {
    // Clean up old entries
    const now = Date.now();
    for (const [ip, data] of requests.entries()) {
      if (now - data.startTime > windowMs) {
        requests.delete(ip);
      }
    }
  }, windowMs);
  
  return (req, res, next) => {
    // Skip if specified
    if (skip(req)) {
      return next();
    }
    
    const ip = req.ip;
    const now = Date.now();
    
    if (!requests.has(ip)) {
      requests.set(ip, {
        count: 1,
        startTime: now,
        resetTime: now + windowMs
      });
      return next();
    }
    
    const data = requests.get(ip);
    
    // Reset if window has passed
    if (now - data.startTime > windowMs) {
      data.count = 1;
      data.startTime = now;
      data.resetTime = now + windowMs;
      return next();
    }
    
    // Check limit
    if (data.count >= max) {
      const retryAfter = Math.ceil((data.resetTime - now) / 1000);
      res.set('Retry-After', retryAfter);
      return res.status(429).json({ error: message });
    }
    
    // Increment count
    data.count++;
    next();
  };
};

// Usage
app.use('/api/', rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 10, // 10 requests per minute
  skip: (req) => req.ip === '127.0.0.1' // Skip for localhost
}));

// Different limits for different routes
app.use('/api/auth/', rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5 // 5 login attempts per 15 minutes
}));

5. Caching Middleware

javascript

const cache = (duration = 60) => { // duration in seconds
  const cacheStore = new Map();
  
  return (req, res, next) => {
    // Only cache GET requests
    if (req.method !== 'GET') {
      return next();
    }
    
    const key = `${req.originalUrl}|${req.headers['accept-language']}`;
    const cached = cacheStore.get(key);
    
    if (cached && Date.now() < cached.expiry) {
      console.log('Serving from cache');
      
      // Set headers
      res.set(cached.headers);
      res.set('X-Cache', 'HIT');
      
      return res.send(cached.body);
    }
    
    // Override res.send to cache response
    const originalSend = res.send;
    res.send = function(body) {
      // Store in cache
      if (res.statusCode === 200) {
        cacheStore.set(key, {
          body,
          headers: {
            'Content-Type': res.get('Content-Type'),
            'Cache-Control': `public, max-age=${duration}`
          },
          expiry: Date.now() + (duration * 1000)
        });
        
        res.set('X-Cache', 'MISS');
        res.set('Cache-Control', `public, max-age=${duration}`);
      }
      
      // Call original send
      originalSend.call(this, body);
    };
    
    next();
  };
};

// Usage
app.get('/api/products', cache(300), (req, res) => { // Cache for 5 minutes
  // Expensive database query
  const products = [
    { id: 1, name: 'Product 1' },
    { id: 2, name: 'Product 2' }
  ];
  res.json(products);
});

// Clear cache manually
app.delete('/cache/clear', (req, res) => {
  cacheStore.clear();
  res.json({ message: 'Cache cleared' });
});

Third-party Middleware

1. CORS (Cross-Origin Resource Sharing)

javascript

const cors = require('cors');

// Basic CORS (allow all origins)
app.use(cors());

// Configure CORS
app.use(cors({
  origin: [
    'http://localhost:3000',
    'https://example.com',
    'https://*.example.com' // Wildcard subdomains
  ],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  exposedHeaders: ['X-Total-Count', 'X-Pagination'],
  credentials: true, // Allow cookies
  maxAge: 86400, // Cache preflight requests (24 hours)
  preflightContinue: false,
  optionsSuccessStatus: 204
}));

// Route-specific CORS
app.get('/api/public', cors(), (req, res) => {
  res.json({ message: 'Public API' });
});

app.get('/api/private', cors({
  origin: 'https://trusted-domain.com'
}), (req, res) => {
  res.json({ message: 'Private API' });
});

// Dynamic origin
app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (like mobile apps)
    if (!origin) return callback(null, true);
    
    const allowedOrigins = ['http://localhost:3000', 'https://example.com'];
    if (allowedOrigins.includes(origin)) {
      return callback(null, true);
    } else {
      return callback(new Error('Not allowed by CORS'));
    }
  }
}));

2. Helmet (Security Headers)

javascript

const helmet = require('helmet');

// Use all Helmet middleware with defaults
app.use(helmet());

// Configure individual security headers
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    scriptSrc: ["'self'", "'unsafe-inline'", "https://cdn.example.com"],
    imgSrc: ["'self'", "data:", "https://images.example.com"],
    connectSrc: ["'self'", "https://api.example.com"]
  }
}));

app.use(helmet.hsts({
  maxAge: 31536000,
  includeSubDomains: true,
  preload: true
}));

// Individual middleware (more control)
app.use(helmet.frameguard({ action: 'deny' })); // Prevent clickjacking
app.use(helmet.noSniff()); // Prevent MIME type sniffing
app.use(helmet.xssFilter()); // Add XSS protection
app.use(helmet.referrerPolicy({ policy: 'same-origin' }));
app.use(helmet.permittedCrossDomainPolicies()); // Cross-domain policy
app.use(helmet.hidePoweredBy()); // Hide X-Powered-By header

// Disable specific headers for certain routes
app.use('/graphql', helmet({
  contentSecurityPolicy: false // Disable CSP for GraphQL playground
}));

3. Morgan (HTTP Request Logger)

javascript

const morgan = require('morgan');
const fs = require('fs');
const path = require('path');

// Predefined formats
app.use(morgan('combined')); // Apache combined log format
app.use(morgan('common')); // Apache common log format
app.use(morgan('dev')); // Concise colored output for development
app.use(morgan('short')); // Shorter than default
app.use(morgan('tiny')); // Minimal output

// Custom format
app.use(morgan(':method :url :status :response-time ms - :res[content-length]'));

// Tokens (custom variables)
morgan.token('type', (req, res) => req.headers['content-type']);
morgan.token('date', () => new Date().toISOString());
morgan.token('user-agent', (req) => req.headers['user-agent']);

app.use(morgan(':date :method :url :status :response-time ms :user-agent'));

// Log to file
const accessLogStream = fs.createWriteStream(
  path.join(__dirname, 'access.log'),
  { flags: 'a' } // Append mode
);

app.use(morgan('combined', { stream: accessLogStream }));

// Conditional logging
app.use(morgan('dev', {
  skip: (req, res) => {
    // Skip logging for health checks
    return req.url === '/health';
  }
}));

// Rotating file logs (with rotating-file-stream or winston)

4. Compression

javascript

const compression = require('compression');

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

// With options
app.use(compression({
  level: 6, // Compression level (0-9)
  threshold: 1024, // Only compress responses > 1KB
  filter: (req, res) => {
    // Don't compress responses with this header
    if (req.headers['x-no-compression']) {
      return false;
    }
    
    // Use compression filter
    return compression.filter(req, res);
  }
}));

// Skip compression for certain routes
app.use((req, res, next) => {
  if (req.url.startsWith('/api/stream')) {
    // Don't compress streaming responses
    next();
  } else {
    compression()(req, res, next);
  }
});

5. Session Management (express-session)

javascript

const session = require('express-session');
const RedisStore = require('connect-redis')(session);

// Basic session
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));

// Redis store for production
app.use(session({
  store: new RedisStore({
    host: 'localhost',
    port: 6379,
    client: redisClient,
    ttl: 86400 // 24 hours in seconds
  }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000
  }
}));

// Usage
app.post('/login', (req, res) => {
  // Set session data
  req.session.userId = user.id;
  req.session.username = user.username;
  req.session.loggedIn = true;
  
  res.json({ message: 'Logged in' });
});

app.get('/profile', (req, res) => {
  if (!req.session.loggedIn) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  
  res.json({ user: req.session.username });
});

app.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Could not log out' });
    }
    res.clearCookie('connect.sid');
    res.json({ message: 'Logged out' });
  });
});

6. File Upload (Multer)

javascript

const multer = require('multer');
const path = require('path');

// Configure storage
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    const ext = path.extname(file.originalname);
    cb(null, file.fieldname + '-' + uniqueSuffix + ext);
  }
});

// File filter
const fileFilter = (req, file, cb) => {
  const allowedTypes = /jpeg|jpg|png|gif|pdf/;
  const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
  const mimetype = allowedTypes.test(file.mimetype);
  
  if (extname && mimetype) {
    return cb(null, true);
  } else {
    cb(new Error('Only images and PDFs are allowed'));
  }
};

const upload = multer({
  storage,
  limits: {
    fileSize: 5 * 1024 * 1024 // 5MB
  },
  fileFilter
});

// Single file upload
app.post('/upload', upload.single('avatar'), (req, res) => {
  console.log('Uploaded file:', req.file);
  res.json({
    message: 'File uploaded',
    file: req.file
  });
});

// Multiple files
app.post('/upload-multiple', upload.array('photos', 10), (req, res) => {
  console.log('Uploaded files:', req.files);
  res.json({
    message: 'Files uploaded',
    files: req.files
  });
});

// Multiple fields with different files
app.post('/upload-fields', upload.fields([
  { name: 'avatar', maxCount: 1 },
  { name: 'gallery', maxCount: 5 }
]), (req, res) => {
  console.log('Avatar:', req.files['avatar'][0]);
  console.log('Gallery:', req.files['gallery']);
  res.json({ message: 'Files uploaded' });
});

Error-handling Middleware

1. Basic Error Handler

javascript

// Error handling middleware MUST have 4 parameters
const errorHandler = (err, req, res, next) => {
  console.error('Error:', err.stack);
  
  // Default error properties
  const status = err.status || err.statusCode || 500;
  const message = err.message || 'Internal Server Error';
  const code = err.code || 'INTERNAL_ERROR';
  
  // Development vs production
  const response = {
    error: {
      code,
      message,
      ...(process.env.NODE_ENV === 'development' && {
        stack: err.stack,
        details: err.details
      })
    }
  };
  
  res.status(status).json(response);
};

// Register as last middleware
app.use(errorHandler);

2. Async Error Handler Wrapper

javascript

// Wrapper to catch async errors
const asyncHandler = (fn) => (req, res, next) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

// Usage
app.get('/async-route', asyncHandler(async (req, res) => {
  const data = await someAsyncOperation();
  if (!data) {
    const error = new Error('Data not found');
    error.statusCode = 404;
    throw error;
  }
  res.json(data);
}));

// For routes with middleware
const validate = asyncHandler(async (req, res, next) => {
  // Async validation logic
  const isValid = await validateToken(req.headers.authorization);
  if (!isValid) {
    throw new Error('Invalid token');
  }
  next();
});

app.get('/protected', validate, (req, res) => {
  res.json({ message: 'Protected route' });
});

3. Custom Error Classes

javascript

// Base error class
class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true; // Operational errors (not programming errors)
    
    Error.captureStackTrace(this, this.constructor);
  }
}

// Specific error classes
class NotFoundError extends AppError {
  constructor(message = 'Resource not found') {
    super(message, 404);
  }
}

class ValidationError extends AppError {
  constructor(message = 'Validation failed', errors = []) {
    super(message, 400);
    this.errors = errors;
  }
}

class AuthenticationError extends AppError {
  constructor(message = 'Authentication required') {
    super(message, 401);
  }
}

class AuthorizationError extends AppError {
  constructor(message = 'Insufficient permissions') {
    super(message, 403);
  }
}

// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    throw new NotFoundError('User not found');
  }
  
  if (user.isPrivate && !req.user.isAdmin) {
    throw new AuthorizationError();
  }
  
  res.json(user);
}));

// Updated error handler
app.use((err, req, res, next) => {
  // Log error
  console.error(err);
  
  // Set defaults for non-AppError instances
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';
  
  // Handle operational vs programming errors
  if (err.isOperational) {
    res.status(err.statusCode).json({
      status: err.status,
      message: err.message,
      ...(err.errors && { errors: err.errors })
    });
  } else {
    // Programming or unknown errors
    console.error('UNEXPECTED ERROR 💥', err);
    
    res.status(500).json({
      status: 'error',
      message: 'Something went wrong'
    });
  }
});

4. 404 Handler

javascript

// Catch-all route for 404
app.use('*', (req, res, next) => {
  // For API routes
  if (req.originalUrl.startsWith('/api')) {
    return next(new NotFoundError(`Cannot ${req.method} ${req.originalUrl}`));
  }
  
  // For web routes
  res.status(404).render('404', {
    title: 'Page Not Found',
    url: req.url
  });
});

// Alternative: Create 404 middleware
const notFoundHandler = (req, res, next) => {
  const error = new Error(`Not Found - ${req.originalUrl}`);
  error.statusCode = 404;
  next(error); // Pass to error handler
};

app.use(notFoundHandler);

5. Global Error Handler with Different Environments

javascript

// Development error handler (detailed)
const sendErrorDev = (err, res) => {
  res.status(err.statusCode).json({
    status: err.status,
    error: err,
    message: err.message,
    stack: err.stack
  });
};

// Production error handler (limited info)
const sendErrorProd = (err, res) => {
  // Operational, trusted error: send message to client
  if (err.isOperational) {
    res.status(err.statusCode).json({
      status: err.status,
      message: err.message
    });
  } 
  // Programming or unknown error: don't leak error details
  else {
    // 1) Log error
    console.error('ERROR ', err);
    
    // 2) Send generic message
    res.status(500).json({
      status: 'error',
      message: 'Something went wrong'
    });
  }
};

// Global error handling middleware
app.use((err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';
  
  if (process.env.NODE_ENV === 'development') {
    sendErrorDev(err, res);
  } else {
    // Handle invalid database IDs, duplicate keys, etc.
    let error = { ...err };
    error.message = err.message;
    
    // Mongoose bad ObjectId
    if (err.name === 'CastError') {
      error = new AppError(`Invalid ${err.path}: ${err.value}`, 400);
    }
    
    // Mongoose duplicate key
    if (err.code === 11000) {
      const field = Object.keys(err.keyValue)[0];
      error = new AppError(`Duplicate field value: ${field}`, 400);
    }
    
    // Mongoose validation error
    if (err.name === 'ValidationError') {
      const errors = Object.values(err.errors).map(el => el.message);
      error = new AppError(`Invalid input data: ${errors.join('. ')}`, 400);
    }
    
    // JWT errors
    if (err.name === 'JsonWebTokenError') {
      error = new AppError('Invalid token', 401);
    }
    
    if (err.name === 'TokenExpiredError') {
      error = new AppError('Token expired', 401);
    }
    
    sendErrorProd(error, res);
  }
});

Complete Middleware Stack Example:

javascript

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const compression = require('compression');
const rateLimit = require('express-rate-limit');

const app = express();

// 1. Security middleware
app.use(helmet());

// 2. CORS
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  credentials: true
}));

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

// 4. Request logging
app.use(morgan(process.env.NODE_ENV === 'development' ? 'dev' : 'combined'));

// 5. Body parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

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

// 7. Custom middleware
app.use((req, res, next) => {
  // Add request ID
  req.id = Date.now().toString(36) + Math.random().toString(36).substr(2);
  
  // Set start time for performance tracking
  req.startTime = Date.now();
  
  next();
});

// 8. Routes
app.use('/api/v1/users', require('./routes/users'));
app.use('/api/v1/auth', require('./routes/auth'));

// 9. 404 handler
app.use('*', (req, res) => {
  res.status(404).json({
    status: 'fail',
    message: `Cannot ${req.method} ${req.originalUrl}`
  });
});

// 10. Global error handler
app.use((err, req, res, next) => {
  console.error(`[${req.id}] Error:`, err);
  
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Internal Server Error';
  
  res.status(statusCode).json({
    status: 'error',
    message,
    requestId: req.id,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
});

// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`);
});

This extensive guide covers middleware from basic to advanced concepts, showing how to create, use, and combine middleware to build robust Express applications.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?