Middleware Lanjutan dan Alur Eksekusi - Perwira Learning Center


1. Latar Belakang

        Memasuki minggu keempat, hari kedua kita masih akan belajar Express.js di Perwira Learning Center dan akan membahas Middleware Lanjutan dan Alur Eksekusi. Jika sebelumnya kita belajar middleware dasar, kini kita akan menyelami lebih dalam tentang bagaimana middleware bekerja di balik layar, bagaimana mengontrol alur eksekusi, dan bagaimana membangun custom middleware yang powerful.

Analogi Middleware Lanjutan: Bandara Internasional

Bayangkan aplikasi Express.js adalah sebuah bandara:

  • Request = Penumpang yang datang
  • Response = Penumpang yang keluar
  • Middleware = Pos-pos pemeriksaan 
    • Security Check = Autentikasi & Authorisasi
    • Imigrasi = Validasi data
    • X-Ray Scanner = Logging & Monitoring
    • Boarding Gate = Rate Limiting
    • Customs = Transformasi data

Setiap middleware bisa:

  • Menghentikan penumpang jika ada masalah (res.json)
  • Memproses dan melanjutkan ke pos berikutnya (next())
  • Melewati beberapa pos tertentu (next('route'))
  • Mengirim ke pos khusus jika ada masalah (next(error))

2. Alat dan Bahan

a. Perangkat Lunak

  1. Node.js & npm - Runtime dan package manager
  2. Express.js - Framework utama
  3. Postman - Testing middleware behavior
  4. VS Code - Code editor
  5. Git - Version control

b. Perangkat Keras

  1. Laptop/PC dengan spesifikasi standar

3. Pembahasan

3.1 Anatomi Middleware Lanjutan

javascript
/**
 * ANATOMI MIDDLEWARE LENGKAP
 * Setiap middleware memiliki akses ke 3/4 parameter:
 * - req    : Request object
 * - res    : Response object  
 * - next   : Function untuk melanjutkan ke middleware berikutnya
 * - err    : Error object (khusus error handler)
 */

// 1. Standard Middleware (3 parameter)
function standardMiddleware(req, res, next) {
  // Proses request
  console.log('Processing...');
  next(); // Lanjut ke middleware berikutnya
}

// 2. Error Handling Middleware (4 parameter)
function errorHandler(err, req, res, next) {
  // Tangani error
  console.error(err.stack);
  res.status(500).send('Something broke!');
}

// 3. Middleware dengan Configuration
function createMiddleware(options) {
  return function(req, res, next) {
    // Gunakan options di sini
    if (options.logging) {
      console.log(req.method, req.url);
    }
    next();
  };
}

// 4. Async Middleware
async function asyncMiddleware(req, res, next) {
  try {
    const data = await fetchData();
    req.data = data;
    next();
  } catch (error) {
    next(error); // Pass error ke error handler
  }
}

3.2 Praktik Lengkap: Sistem Middleware Enterprise

Mari kita bangun sistem middleware yang kompleks untuk aplikasi enterprise:

bash
# 1. Buat folder project
mkdir enterprise-middleware
cd enterprise-middleware

# 2. Inisialisasi project
npm init -y

# 3. Install dependencies
npm install express dotenv cors helmet morgan compression
npm install express-rate-limit express-slow-down
npm install jsonwebtoken bcryptjs joi
npm install winston winston-daily-rotate-file
npm install -D nodemon

# 4. Buat struktur folder
mkdir -p src/{middleware,config,utils,services,models,routes}
 
A. Logger Middleware - Winston Implementation

src/utils/logger.js

javascript
/**
 * LOGGER MIDDLEWARE - Winston Advanced Logger
 * Fitur:
 * - Multiple transports (console, file, daily rotate)
 * - Different log levels per environment
 * - Request/Response logging
 * - Error tracking
 * - Performance monitoring
 */

const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');

// Define custom log levels
const levels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4,
  audit: 5
};

// Define colors
const colors = {
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'blue',
  audit: 'cyan'
};

winston.addColors(colors);

// Custom format untuk audit trail
const auditFormat = winston.format.printf(({ timestamp, level, message, ...meta }) => {
  return JSON.stringify({
    timestamp,
    level,
    ...meta,
    message
  });
});

// Create logger instance
const logger = winston.createLogger({
  levels,
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.errors({ stack: true }),
    winston.format.splat(),
    winston.format.json()
  ),
  transports: [
    // Console transport untuk development
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize({ all: true }),
        winston.format.printf(
          (info) => `${info.timestamp} ${info.level}: ${info.message}`
        )
      )
    }),
    
    // File transport untuk semua logs
    new DailyRotateFile({
      filename: 'logs/application-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
      format: winston.format.combine(
        winston.format.uncolorize(),
        winston.format.json()
      )
    }),
    
    // Separate file untuk audit trail
    new DailyRotateFile({
      filename: 'logs/audit-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '30d',
      level: 'audit',
      format: winston.format.combine(
        winston.format.uncolorize(),
        auditFormat
      )
    }),
    
    // Separate file untuk errors
    new DailyRotateFile({
      filename: 'logs/error-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '30d',
      level: 'error'
    })
  ]
});

// Middleware untuk logging HTTP requests
logger.httpLogger = function() {
  return function(req, res, next) {
    const start = process.hrtime();
    
    // Generate unique request ID
    req.requestId = req.headers['x-request-id'] || 
                    `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    // Log request
    logger.http({
      message: `${req.method} ${req.originalUrl}`,
      requestId: req.requestId,
      method: req.method,
      url: req.originalUrl,
      ip: req.ip || req.connection.remoteAddress,
      userAgent: req.headers['user-agent'],
      userId: req.user?.id,
      timestamp: new Date().toISOString()
    });
    
    // Override res.end untuk log response
    const originalEnd = res.end;
    res.end = function(chunk, encoding) {
      const duration = process.hrtime(start);
      const durationMs = (duration[0] * 1e3 + duration[1] * 1e-6).toFixed(2);
      
      // Log response
      logger.http({
        message: `${req.method} ${req.originalUrl} ${res.statusCode}`,
        requestId: req.requestId,
        statusCode: res.statusCode,
        duration: `${durationMs}ms`,
        contentLength: res.getHeader('content-length') || 0
      });
      
      originalEnd.call(this, chunk, encoding);
    };
    
    next();
  };
};

// Middleware untuk audit trail
logger.audit = function(action, userId, details = {}) {
  logger.log('audit', {
    message: `Audit: ${action}`,
    action,
    userId,
    ...details,
    timestamp: new Date().toISOString()
  });
};

module.exports = logger;

B. Security Middleware Suite

src/middleware/security.middleware.js

javascript
/**
 * SECURITY MIDDLEWARE SUITE
 * Komprehensif: Helmet, CORS, CSP, HSTS, XSS Protection
 */

const helmet = require('helmet');
const cors = require('cors');
const config = require('../config/env');
const logger = require('../utils/logger');

class SecurityMiddleware {
  /**
   * Advanced Helmet Configuration
   */
  static helmetConfig() {
    return helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          scriptSrc: ["'self'"],
          imgSrc: ["'self'", "data:", "https:"],
          connectSrc: ["'self'", config.API_URL],
          fontSrc: ["'self'", "https:"],
          objectSrc: ["'none'"],
          mediaSrc: ["'self'"],
          frameSrc: ["'none'"],
        },
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      },
      referrerPolicy: {
        policy: 'strict-origin-when-cross-origin'
      },
      noSniff: true,
      xssFilter: true,
      hidePoweredBy: true,
      frameguard: {
        action: 'deny'
      }
    });
  }

  /**
   * Dynamic CORS Configuration
   */
  static corsConfig() {
    const whitelist = config.CORS_WHITELIST || [
      'http://localhost:3000',
      'http://localhost:5173',
      'https://yourdomain.com'
    ];

    return cors({
      origin: function(origin, callback) {
        // Allow requests with no origin (like mobile apps, curl, Postman)
        if (!origin || whitelist.indexOf(origin) !== -1) {
          callback(null, true);
        } else {
          logger.warn(`CORS blocked: ${origin}`);
          callback(new Error('Not allowed by CORS'));
        }
      },
      credentials: true,
      methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
      allowedHeaders: [
        'Content-Type',
        'Authorization',
        'X-Request-ID',
        'X-API-Key',
        'X-CSRF-Token'
      ],
      exposedHeaders: [
        'X-RateLimit-Limit',
        'X-RateLimit-Remaining',
        'X-RateLimit-Reset',
        'X-Request-ID'
      ],
      maxAge: 86400 // 24 hours
    });
  }

  /**
   * CSRF Protection (untuk form submissions)
   */
  static csrfProtection() {
    return (req, res, next) => {
      // Untuk API, biasanya pakai token di header
      const csrfToken = req.headers['x-csrf-token'];
      const sessionToken = req.session?.csrfToken;
      
      if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
        if (!csrfToken || !sessionToken || csrfToken !== sessionToken) {
          logger.warn('CSRF attack detected', {
            ip: req.ip,
            method: req.method,
            url: req.url
          });
          
          return res.status(403).json({
            success: false,
            error: 'CSRF token invalid'
          });
        }
      }
      
      next();
    };
  }

  /**
   * API Key Authentication
   */
  static apiKeyAuth() {
    return (req, res, next) => {
      const apiKey = req.headers['x-api-key'];
      const validApiKeys = config.API_KEYS || ['dev-key-123', 'prod-key-456'];
      
      if (!apiKey || !validApiKeys.includes(apiKey)) {
        logger.warn(`Invalid API key attempt: ${apiKey}`);
        
        return res.status(401).json({
          success: false,
          error: 'Invalid API key'
        });
      }
      
      req.apiKey = apiKey;
      next();
    };
  }

  /**
   * Sanitization Middleware (XSS Prevention)
   */
  static sanitize() {
    return (req, res, next) => {
      // Sanitize request body
      if (req.body) {
        Object.keys(req.body).forEach(key => {
          if (typeof req.body[key] === 'string') {
            // Basic XSS sanitization
            req.body[key] = req.body[key]
              .replace(/</g, '&lt;')
              .replace(/>/g, '&gt;')
              .replace(/"/g, '&quot;')
              .replace(/'/g, '&#x27;')
              .replace(/\//g, '&#x2F;');
          }
        });
      }
      
      // Sanitize query parameters
      if (req.query) {
        Object.keys(req.query).forEach(key => {
          if (typeof req.query[key] === 'string') {
            req.query[key] = req.query[key]
              .replace(/</g, '&lt;')
              .replace(/>/g, '&gt;');
          }
        });
      }
      
      next();
    };
  }

  /**
   * SQL Injection Prevention (Basic)
   */
  static preventSQLInjection() {
    const sqlPattern = /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|WHERE|FROM|JOIN)\b)|('--)|(;\s*$)/i;
    
    return (req, res, next) => {
      const checkValue = (value) => {
        if (typeof value === 'string' && sqlPattern.test(value)) {
          return true;
        }
        return false;
      };
      
      // Check query parameters
      for (const key in req.query) {
        if (checkValue(req.query[key])) {
          logger.warn(`SQL injection attempt detected in query: ${key}=${req.query[key]}`);
          return res.status(400).json({
            success: false,
            error: 'Invalid input detected'
          });
        }
      }
      
      // Check body
      if (req.body) {
        for (const key in req.body) {
          if (checkValue(req.body[key])) {
            logger.warn(`SQL injection attempt detected in body: ${key}=${req.body[key]}`);
            return res.status(400).json({
              success: false,
              error: 'Invalid input detected'
            });
          }
        }
      }
      
      next();
    };
  }
}

module.exports = SecurityMiddleware;

C. Advanced Rate Limiting

src/middleware/rate-limit.middleware.js

javascript
/**
 * ADVANCED RATE LIMITING MIDDLEWARE
 * Fitur:
 * - Per-user & per-IP rate limiting
 * - Different limits per endpoint
 * - Sliding window algorithm
 * - Graceful degradation
 * - Redis support for distributed systems
 */

const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const config = require('../config/env');
const logger = require('../utils/logger');

class RateLimitMiddleware {
  constructor() {
    this.redis = config.REDIS_URL ? new Redis(config.REDIS_URL) : null;
  }

  /**
   * Standard rate limiter untuk semua endpoint
   */
  static standard() {
    return rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // Limit each IP to 100 requests per windowMs
      message: {
        success: false,
        error: 'Too many requests',
        message: 'Please try again later'
      },
      standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
      legacyHeaders: false, // Disable the `X-RateLimit-*` headers
      keyGenerator: (req) => {
        // Use user ID if authenticated, otherwise IP
        return req.user?.id || req.ip;
      },
      handler: (req, res) => {
        logger.warn('Rate limit exceeded', {
          ip: req.ip,
          userId: req.user?.id,
          path: req.path
        });
        
        res.status(429).json({
          success: false,
          error: 'RATE_LIMIT_EXCEEDED',
          message: 'Too many requests, please try again later',
          retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
        });
      },
      skip: (req) => {
        // Skip rate limiting for certain conditions
        return req.path === '/health' || req.user?.role === 'admin';
      }
    });
  }

  /**
   * Strict rate limiter untuk auth endpoints
   */
  static strict() {
    return rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 5, // Only 5 requests per 15 minutes
      skipSuccessfulRequests: true, // Don't count successful requests
      message: {
        success: false,
        error: 'Too many authentication attempts',
        message: 'Please try again after 15 minutes'
      },
      keyGenerator: (req) => {
        // Auth attempts are tracked by email or IP
        return req.body.email || req.ip;
      }
    });
  }

  /**
   * Slow down middleware (gradual throttling)
   */
  static slowDown() {
    return slowDown({
      windowMs: 15 * 60 * 1000, // 15 minutes
      delayAfter: 50, // Allow 50 requests without delay
      delayMs: (hits) => hits * 100, // Add 100ms delay per hit above limit
      maxDelayMs: 10000, // Max 10 second delay
      skip: (req) => req.user?.role === 'admin'
    });
  }

  /**
   * Per-endpoint custom rate limiter factory
   */
  static endpointLimiter(options = {}) {
    const {
      windowMs = 60 * 1000,
      max = 30,
      message = 'Too many requests to this endpoint',
      keyGenerator = (req) => req.user?.id || req.ip
    } = options;

    return rateLimit({
      windowMs,
      max,
      message: {
        success: false,
        error: 'ENDPOINT_RATE_LIMIT',
        message
      },
      keyGenerator,
      standardHeaders: true,
      legacyHeaders: false
    });
  }

  /**
   * Concurrent request limiter
   */
  static concurrent(limit = 10) {
    const activeRequests = new Map();
    
    return (req, res, next) => {
      const key = req.user?.id || req.ip;
      const current = activeRequests.get(key) || 0;
      
      if (current >= limit) {
        logger.warn(`Concurrent request limit exceeded for ${key}`);
        
        return res.status(429).json({
          success: false,
          error: 'CONCURRENT_LIMIT_EXCEEDED',
          message: 'Too many concurrent requests',
          limit
        });
      }
      
      activeRequests.set(key, current + 1);
      
      const complete = () => {
        const remaining = activeRequests.get(key) || 1;
        if (remaining <= 1) {
          activeRequests.delete(key);
        } else {
          activeRequests.set(key, remaining - 1);
        }
      };
      
      res.on('finish', complete);
      res.on('error', complete);
      
      next();
    };
  }

  /**
   * Bandwidth limiter
   */
  static bandwidth(limitBytes = 1024 * 1024) { // 1MB default
    const usage = new Map();
    
    return (req, res, next) => {
      const key = req.user?.id || req.ip;
      const used = usage.get(key) || 0;
      
      if (used >= limitBytes) {
        logger.warn(`Bandwidth limit exceeded for ${key}`);
        
        return res.status(429).json({
          success: false,
          error: 'BANDWIDTH_LIMIT_EXCEEDED',
          message: 'Bandwidth limit exceeded'
        });
      }
      
      // Track response size
      const originalWrite = res.write;
      const originalEnd = res.end;
      let bytesWritten = 0;
      
      res.write = function(chunk, encoding) {
        bytesWritten += Buffer.byteLength(chunk, encoding);
        return originalWrite.call(this, chunk, encoding);
      };
      
      res.end = function(chunk, encoding) {
        if (chunk) {
          bytesWritten += Buffer.byteLength(chunk, encoding);
        }
        
        const totalUsed = used + bytesWritten;
        usage.set(key, totalUsed);
        
        // Add bandwidth headers
        res.setHeader('X-Bandwidth-Used', bytesWritten);
        res.setHeader('X-Bandwidth-Limit', limitBytes);
        res.setHeader('X-Bandwidth-Remaining', limitBytes - totalUsed);
        
        return originalEnd.call(this, chunk, encoding);
      };
      
      next();
    };
  }
}

module.exports = RateLimitMiddleware;

D. Authentication & Authorization Suite

src/middleware/auth.middleware.js

javascript
/**
 * ADVANCED AUTHENTICATION MIDDLEWARE
 * Fitur:
 * - JWT token validation
 * - Role-based access control (RBAC)
 * - Permission-based access control
 * - Token refresh mechanism
 * - Device fingerprinting
 * - Session management
 */

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const config = require('../config/env');
const logger = require('../utils/logger');
const Redis = require('ioredis');

class AuthMiddleware {
  constructor() {
    this.redis = config.REDIS_URL ? new Redis(config.REDIS_URL) : null;
    this.secret = config.JWT_SECRET;
    this.refreshSecret = config.JWT_REFRESH_SECRET || this.secret;
  }

  /**
   * Generate device fingerprint
   */
  static generateFingerprint(req) {
    const components = [
      req.headers['user-agent'] || '',
      req.headers['accept-language'] || '',
      req.headers['sec-ch-ua'] || '',
      req.ip || '',
      req.headers['accept-encoding'] || ''
    ].join('|');
    
    return crypto
      .createHash('sha256')
      .update(components)
      .digest('hex');
  }

  /**
   * Main authentication middleware
   */
  authenticate() {
    return async (req, res, next) => {
      try {
        // Extract token from various locations
        const token = this.extractToken(req);
        
        if (!token) {
          throw {
            name: 'AuthenticationError',
            message: 'No token provided',
            code: 'NO_TOKEN',
            statusCode: 401
          };
        }

        // Verify token
        const decoded = await this.verifyToken(token, this.secret);
        
        // Check if token is blacklisted
        if (this.redis) {
          const isBlacklisted = await this.redis.get(`blacklist:${token}`);
          if (isBlacklisted) {
            throw {
              name: 'AuthenticationError',
              message: 'Token has been revoked',
              code: 'TOKEN_REVOKED',
              statusCode: 401
            };
          }
        }

        // Verify device fingerprint
        const fingerprint = AuthMiddleware.generateFingerprint(req);
        if (decoded.fingerprint && decoded.fingerprint !== fingerprint) {
          logger.warn(`Device fingerprint mismatch`, {
            userId: decoded.sub,
            expected: decoded.fingerprint,
            received: fingerprint
          });
          
          throw {
            name: 'AuthenticationError',
            message: 'Invalid device fingerprint',
            code: 'INVALID_DEVICE',
            statusCode: 401
          };
        }

        // Attach user info to request
        req.user = {
          id: decoded.sub,
          email: decoded.email,
          role: decoded.role,
          permissions: decoded.permissions || []
        };
        req.token = token;
        req.tokenDecoded = decoded;

        // Log successful authentication
        logger.info('User authenticated', {
          userId: req.user.id,
          role: req.user.role,
          path: req.path,
          requestId: req.requestId
        });

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Refresh token authentication
   */
  authenticateRefresh() {
    return async (req, res, next) => {
      try {
        const refreshToken = req.body.refreshToken || 
                            req.headers['x-refresh-token'];
        
        if (!refreshToken) {
          throw {
            name: 'AuthenticationError',
            message: 'No refresh token provided',
            code: 'NO_REFRESH_TOKEN',
            statusCode: 401
          };
        }

        const decoded = await this.verifyToken(refreshToken, this.refreshSecret);
        
        // Verify in Redis
        if (this.redis) {
          const stored = await this.redis.get(`refresh:${decoded.sub}`);
          if (stored !== refreshToken) {
            throw {
              name: 'AuthenticationError',
              message: 'Invalid refresh token',
              code: 'INVALID_REFRESH_TOKEN',
              statusCode: 401
            };
          }
        }

        req.refreshToken = refreshToken;
        req.user = { id: decoded.sub };
        
        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Role-based authorization
   */
  authorize(roles = []) {
    return (req, res, next) => {
      try {
        if (!req.user) {
          throw {
            name: 'AuthorizationError',
            message: 'User not authenticated',
            code: 'NOT_AUTHENTICATED',
            statusCode: 401
          };
        }

        if (roles.length && !roles.includes(req.user.role)) {
          logger.warn('Authorization failed', {
            userId: req.user.id,
            requiredRole: roles,
            userRole: req.user.role,
            path: req.path
          });

          throw {
            name: 'AuthorizationError',
            message: 'Insufficient permissions',
            code: 'FORBIDDEN',
            statusCode: 403
          };
        }

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Permission-based authorization (fine-grained)
   */
  hasPermission(permissions = []) {
    return (req, res, next) => {
      try {
        if (!req.user) {
          throw {
            name: 'AuthorizationError',
            message: 'User not authenticated',
            code: 'NOT_AUTHENTICATED',
            statusCode: 401
          };
        }

        const userPermissions = req.user.permissions || [];
        const hasAllPermissions = permissions.every(p => 
          userPermissions.includes(p) || req.user.role === 'admin'
        );

        if (!hasAllPermissions) {
          logger.warn('Permission denied', {
            userId: req.user.id,
            requiredPermissions: permissions,
            userPermissions
          });

          throw {
            name: 'AuthorizationError',
            message: 'Missing required permissions',
            code: 'PERMISSION_DENIED',
            statusCode: 403,
            details: { required: permissions }
          };
        }

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Resource ownership verification
   */
  verifyOwnership(resourceGetter) {
    return async (req, res, next) => {
      try {
        const resource = await resourceGetter(req);
        
        if (!resource) {
          throw {
            name: 'NotFoundError',
            message: 'Resource not found',
            code: 'NOT_FOUND',
            statusCode: 404
          };
        }

        // Admin can access any resource
        if (req.user.role === 'admin') {
          return next();
        }

        // Check ownership
        if (resource.userId !== req.user.id && resource.authorId !== req.user.id) {
          logger.warn('Ownership verification failed', {
            userId: req.user.id,
            resourceId: req.params.id,
            resourceOwner: resource.userId || resource.authorId
          });

          throw {
            name: 'AuthorizationError',
            message: 'You do not own this resource',
            code: 'NOT_OWNER',
            statusCode: 403
          };
        }

        next();
      } catch (error) {
        next(error);
      }
    };
  }

  /**
   * Extract token from request
   */
  extractToken(req) {
    // Check Authorization header
    const authHeader = req.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.split(' ')[1];
    }

    // Check query parameter
    if (req.query.token) {
      return req.query.token;
    }

    // Check cookie
    if (req.cookies && req.cookies.token) {
      return req.cookies.token;
    }

    return null;
  }

  /**
   * Verify JWT token
   */
  async verifyToken(token, secret) {
    try {
      const decoded = jwt.verify(token, secret);
      return decoded;
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        throw {
          name: 'AuthenticationError',
          message: 'Token expired',
          code: 'TOKEN_EXPIRED',
          statusCode: 401
        };
      }
      
      throw {
        name: 'AuthenticationError',
        message: 'Invalid token',
        code: 'INVALID_TOKEN',
        statusCode: 401
      };
    }
  }

  /**
   * Optional authentication (doesn't throw error)
   */
  optional() {
    return async (req, res, next) => {
      try {
        const token = this.extractToken(req);
        
        if (token) {
          const decoded = await this.verifyToken(token, this.secret);
          req.user = {
            id: decoded.sub,
            email: decoded.email,
            role: decoded.role
          };
        }
        
        next();
      } catch {
        // Silently fail, proceed without user
        next();
      }
    };
  }
}

module.exports = new AuthMiddleware();

E. Validation Middleware Factory

src/middleware/validation.middleware.js

javascript
/**
 * ADVANCED VALIDATION MIDDLEWARE
 * Fitur:
 * - Schema validation with Joi
 * - Conditional validation
 * - Async validation
 * - Custom validators
 * - Validation error formatting
 */

const Joi = require('joi');
const logger = require('../utils/logger');

class ValidationMiddleware {
  /**
   * Main validation middleware factory
   */
  static validate(schema, property = 'body', options = {}) {
    return async (req, res, next) => {
      try {
        const {
          abortEarly = false,
          stripUnknown = true,
          allowUnknown = false,
          convert = true,
          context = {}
        } = options;

        // Add request context to schema
        const validationContext = {
          ...context,
          user: req.user,
          params: req.params,
          query: req.query
        };

        // Apply conditional validation
        const finalSchema = typeof schema === 'function'
          ? schema(validationContext)
          : schema;

        const value = await finalSchema.validateAsync(req[property], {
          abortEarly,
          stripUnknown,
          allowUnknown,
          convert,
          context: validationContext
        });

        // Replace request data with validated data
        req[property] = value;
        
        // Log validation success
        logger.debug('Validation passed', {
          property,
          path: req.path
        });

        next();
      } catch (error) {
        if (error.isJoi) {
          const errors = error.details.map(detail => ({
            field: detail.path.join('.'),
            message: detail.message.replace(/"/g, ''),
            type: detail.type,
            context: detail.context
          }));

          logger.warn('Validation failed', {
            property,
            errors,
            path: req.path,
            body: req.body
          });

          return res.status(400).json({
            success: false,
            error: 'VALIDATION_ERROR',
            message: 'Validation failed',
            errors
          });
        }

        next(error);
      }
    };
  }

  /**
   * Dynamic schema builder
   */
  static createSchema(rules) {
    const schema = {};
    
    Object.entries(rules).forEach(([field, rule]) => {
      let validator = Joi[rule.type]();
      
      // Apply constraints
      if (rule.required) validator = validator.required();
      if (rule.min) validator = validator.min(rule.min);
      if (rule.max) validator = validator.max(rule.max);
      if (rule.email) validator = validator.email();
      if (rule.pattern) validator = validator.pattern(rule.pattern);
      if (rule.default) validator = validator.default(rule.default);
      if (rule.valid) validator = validator.valid(...rule.valid);
      if (rule.invalid) validator = validator.invalid(...rule.invalid);
      
      schema[field] = validator;
    });
    
    return Joi.object(schema);
  }

  /**
   * Conditional validation middleware
   */
  static conditional(condition, schema) {
    return async (req, res, next) => {
      const shouldValidate = typeof condition === 'function'
        ? condition(req)
        : condition;
      
      if (shouldValidate) {
        return this.validate(schema)(req, res, next);
      }
      
      next();
    };
  }

  /**
   * File validation middleware (for multer)
   */
  static validateFile(options = {}) {
    const {
      maxSize = 5 * 1024 * 1024, // 5MB
      allowedTypes = ['image/jpeg', 'image/png', 'image/gif'],
      maxCount = 1
    } = options;

    return (req, res, next) => {
      const files = req.files || (req.file ? [req.file] : []);
      
      if (files.length === 0) {
        return next();
      }

      const errors = [];

      files.forEach((file, index) => {
        // Check file size
        if (file.size > maxSize) {
          errors.push({
            field: file.fieldname,
            index,
            error: `File too large. Max size: ${maxSize / 1024 / 1024}MB`,
            current: `${(file.size / 1024 / 1024).toFixed(2)}MB`
          });
        }

        // Check file type
        if (!allowedTypes.includes(file.mimetype)) {
          errors.push({
            field: file.fieldname,
            index,
            error: 'Invalid file type',
            allowed: allowedTypes,
            received: file.mimetype
          });
        }
      });

      if (errors.length > 0) {
        return res.status(400).json({
          success: false,
          error: 'FILE_VALIDATION_ERROR',
          message: 'File validation failed',
          errors
        });
      }

      next();
    };
  }
}

module.exports = ValidationMiddleware;

F. Performance & Monitoring Middleware

src/middleware/performance.middleware.js

javascript
/**
 * PERFORMANCE & MONITORING MIDDLEWARE
 * Fitur:
 * - Response time tracking
 * - Memory usage monitoring
 * - CPU usage tracking
 * - Slow request detection
 * - Performance metrics
 */

const os = require('os');
const process = require('process');
const logger = require('../utils/logger');

class PerformanceMiddleware {
  constructor() {
    this.slowRequests = [];
  }

  /**
   * Response time tracker
   */
  static responseTime() {
    return (req, res, next) => {
      const start = process.hrtime();
      
      res.on('finish', () => {
        const diff = process.hrtime(start);
        const responseTime = diff[0] * 1e3 + diff[1] * 1e-6;
        
        // Add response time header
        res.setHeader('X-Response-Time', `${responseTime.toFixed(2)}ms`);
        
        // Log slow requests
        if (responseTime > 1000) {
          logger.warn('Slow request detected', {
            method: req.method,
            url: req.originalUrl,
            responseTime: `${responseTime.toFixed(2)}ms`,
            userId: req.user?.id
          });
        }
      });
      
      next();
    };
  }

  /**
   * Memory usage monitor
   */
  static memoryUsage() {
    return (req, res, next) => {
      const usage = process.memoryUsage();
      const memoryThreshold = 500 * 1024 * 1024; // 500MB
      
      if (usage.heapUsed > memoryThreshold) {
        logger.warn('High memory usage detected', {
          heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)}MB`,
          heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)}MB`,
          rss: `${(usage.rss / 1024 / 1024).toFixed(2)}MB`
        });
      }
      
      // Add memory header
      res.setHeader('X-Memory-Usage', `${(usage.heapUsed / 1024 / 1024).toFixed(2)}MB`);
      
      next();
    };
  }

  /**
   * CPU usage monitor
   */
  static cpuUsage() {
    let lastCheck = process.cpuUsage();
    let lastTime = Date.now();
    
    return (req, res, next) => {
      const currentTime = Date.now();
      const timeDiff = currentTime - lastTime;
      
      if (timeDiff > 5000) { // Check every 5 seconds
        const currentUsage = process.cpuUsage();
        const userDiff = currentUsage.user - lastCheck.user;
        const systemDiff = currentUsage.system - lastCheck.system;
        
        const cpuPercent = ((userDiff + systemDiff) / (timeDiff * 1000)) * 100;
        
        if (cpuPercent > 80) {
          logger.warn('High CPU usage detected', {
            cpuPercent: `${cpuPercent.toFixed(2)}%`,
            user: `${(userDiff / 1000000).toFixed(2)}s`,
            system: `${(systemDiff / 1000000).toFixed(2)}s`
          });
        }
        
        lastCheck = currentUsage;
        lastTime = currentTime;
      }
      
      next();
    };
  }

  /**
   * Request throttling (adaptive rate limiting)
   */
  static adaptiveThrottling() {
    const requestTimes = [];
    const windowMs = 60000; // 1 minute
    
    return (req, res, next) => {
      const now = Date.now();
      
      // Clean old entries
      while (requestTimes.length && requestTimes[0] < now - windowMs) {
        requestTimes.shift();
      }
      
      // Add current request
      requestTimes.push(now);
      
      // Calculate requests per second
      const rps = requestTimes.length / (windowMs / 1000);
      
      // Adaptive delay based on load
      if (rps > 50) {
        // High load - add delay
        const delay = Math.min((rps - 50) * 5, 1000); // Max 1 second delay
        
        setTimeout(() => {
          res.setHeader('X-Adaptive-Delay', `${delay}ms`);
          res.setHeader('X-Requests-Per-Second', rps.toFixed(2));
          next();
        }, delay);
      } else {
        res.setHeader('X-Requests-Per-Second', rps.toFixed(2));
        next();
      }
    };
  }

  /**
   * Database query performance monitor
   */
  static queryPerformance() {
    return (req, res, next) => {
      // Store original query method
      const originalQuery = req.db?.query;
      
      if (req.db) {
        req.db.query = async function(sql, params) {
          const start = process.hrtime();
          
          try {
            const result = await originalQuery.call(this, sql, params);
            
            const diff = process.hrtime(start);
            const duration = diff[0] * 1e3 + diff[1] * 1e-6;
            
            // Log slow queries
            if (duration > 100) {
              logger.warn('Slow query detected', {
                duration: `${duration.toFixed(2)}ms`,
                sql: sql.substring(0, 200),
                params
              });
            }
            
            return result;
          } catch (error) {
            const diff = process.hrtime(start);
            const duration = diff[0] * 1e3 + diff[1] * 1e-6;
            
            logger.error('Query failed', {
              duration: `${duration.toFixed(2)}ms`,
              sql: sql.substring(0, 200),
              error: error.message
            });
            
            throw error;
          }
        };
      }
      
      next();
    };
  }

  /**
   * Request queue monitor
   */
  static requestQueue() {
    let activeRequests = 0;
    let maxConcurrent = 0;
    
    return (req, res, next) => {
      activeRequests++;
      maxConcurrent = Math.max(maxConcurrent, activeRequests);
      
      // Add metrics header
      res.setHeader('X-Active-Requests', activeRequests);
      res.setHeader('X-Max-Concurrent', maxConcurrent);
      
      const complete = () => {
        activeRequests--;
      };
      
      res.on('finish', complete);
      res.on('error', complete);
      
      next();
    };
  }
}

module.exports = PerformanceMiddleware;

G. Data Transformation Middleware

src/middleware/transform.middleware.js

javascript
/**
 * DATA TRANSFORMATION MIDDLEWARE
 * Fitur:
 * - Request/Response transformation
 * - Pagination formatting
 * - Field filtering
 * - Data enrichment
 * - Format conversion
 */

class TransformMiddleware {
  /**
   * Transform request body
   */
  static transformRequest(transformFn) {
    return (req, res, next) => {
      if (req.body) {
        req.body = transformFn(req.body, req);
      }
      next();
    };
  }

  /**
   * Transform response data
   */
  static transformResponse(transformFn) {
    return (req, res, next) => {
      const originalJson = res.json;
      
      res.json = function(data) {
        const transformed = transformFn(data, req);
        return originalJson.call(this, transformed);
      };
      
      next();
    };
  }

  /**
   * Format pagination
   */
  static paginate() {
    return (req, res, next) => {
      // Parse pagination parameters
      req.pagination = {
        page: Math.max(parseInt(req.query.page) || 1, 1),
        limit: Math.min(
          Math.max(parseInt(req.query.limit) || 10, 1),
          100
        )
      };
      
      req.pagination.offset = (req.pagination.page - 1) * req.pagination.limit;
      
      // Store original json
      const originalJson = res.json;
      
      res.json = function(data) {
        if (data && data.rows !== undefined) {
          const { rows, count } = data;
          
          const paginatedResponse = {
            data: rows,
            pagination: {
              page: req.pagination.page,
              limit: req.pagination.limit,
              totalItems: count,
              totalPages: Math.ceil(count / req.pagination.limit),
              hasNext: req.pagination.page < Math.ceil(count / req.pagination.limit),
              hasPrev: req.pagination.page > 1
            }
          };
          
          return originalJson.call(this, paginatedResponse);
        }
        
        return originalJson.call(this, data);
      };
      
      next();
    };
  }

  /**
   * Field filtering (sparse fields)
   */
  static filterFields() {
    return (req, res, next) => {
      const fields = req.query.fields 
        ? req.query.fields.split(',')
        : null;
      
      if (fields) {
        const originalJson = res.json;
        
        res.json = function(data) {
          if (Array.isArray(data)) {
            const filtered = data.map(item => {
              const filteredItem = {};
              fields.forEach(field => {
                if (item[field] !== undefined) {
                  filteredItem[field] = item[field];
                }
              });
              return filteredItem;
            });
            return originalJson.call(this, filtered);
          }
          
          if (data && typeof data === 'object') {
            const filtered = {};
            fields.forEach(field => {
              if (data[field] !== undefined) {
                filtered[field] = data[field];
              }
            });
            return originalJson.call(this, filtered);
          }
          
          return originalJson.call(this, data);
        };
      }
      
      next();
    };
  }

  /**
   * Data enrichment (add computed fields)
   */
  static enrich(enricher) {
    return (req, res, next) => {
      const originalJson = res.json;
      
      res.json = function(data) {
        if (data && data.data) {
          data.data = enricher(data.data, req);
        } else if (data && Array.isArray(data)) {
          data = enricher(data, req);
        }
        
        return originalJson.call(this, data);
      };
      
      next();
    };
  }

  /**
   * Format dates consistently
   */
  static formatDate(format = 'ISO') {
    return (req, res, next) => {
      const originalJson = res.json;
      
      res.json = function(data) {
        const formatDates = (obj) => {
          if (!obj || typeof obj !== 'object') return;
          
          Object.keys(obj).forEach(key => {
            if (obj[key] instanceof Date) {
              if (format === 'ISO') {
                obj[key] = obj[key].toISOString();
              } else if (format === 'timestamp') {
                obj[key] = obj[key].getTime();
              } else if (format === 'locale') {
                obj[key] = obj[key].toLocaleString();
              }
            } else if (typeof obj[key] === 'object') {
              formatDates(obj[key]);
            }
          });
        };
        
        formatDates(data);
        return originalJson.call(this, data);
      };
      
      next();
    };
  }

  /**
   * JSON:API formatter
   */
  static jsonApi() {
    return (req, res, next) => {
      const originalJson = res.json;
      
      res.json = function(data) {
        let response = {};
        
        if (data && data.data !== undefined) {
          // Already formatted
          response = data;
        } else if (Array.isArray(data)) {
          // Collection
          response = {
            data: data.map(item => ({
              type: req.baseUrl.split('/').pop() || 'resource',
              id: item.id,
              attributes: item
            }))
          };
        } else if (data && typeof data === 'object') {
          // Single resource
          response = {
            data: {
              type: req.baseUrl.split('/').pop() || 'resource',
              id: data.id,
              attributes: data
            }
          };
        }
        
        // Add links
        response.links = {
          self: `${req.protocol}://${req.get('host')}${req.originalUrl}`
        };
        
        return originalJson.call(this, response);
      };
      
      next();
    };
  }
}

module.exports = TransformMiddleware;

H. Error Handling Middleware Suite

src/middleware/error.middleware.js

javascript
/**
 * ADVANCED ERROR HANDLING MIDDLEWARE
 * Fitur:
 * - Multi-level error handling
 * - Error classification
 * - Graceful degradation
 * - Fallback responses
 * - Error reporting
 */

const logger = require('../utils/logger');
const config = require('../config/env');

class ErrorMiddleware {
  /**
   * 404 Not Found Handler
   */
  static notFound() {
    return (req, res, next) => {
      const error = {
        name: 'NotFoundError',
        message: `Cannot ${req.method} ${req.originalUrl}`,
        code: 'NOT_FOUND',
        statusCode: 404
      };
      
      next(error);
    };
  }

  /**
   * Database error handler
   */
  static databaseError() {
    return (err, req, res, next) => {
      if (err.name === 'SequelizeError' || err.name === 'MongoError') {
        logger.error('Database error', {
          error: err.message,
          code: err.code,
          sql: err.sql,
          path: req.path
        });
        
        return res.status(503).json({
          success: false,
          error: 'DATABASE_ERROR',
          message: 'Database service unavailable',
          retryAfter: 30
        });
      }
      
      next(err);
    };
  }

  /**
   * Validation error handler
   */
  static validationError() {
    return (err, req, res, next) => {
      if (err.name === 'ValidationError') {
        return res.status(400).json({
          success: false,
          error: 'VALIDATION_ERROR',
          message: err.message,
          errors: err.errors
        });
      }
      
      next(err);
    };
  }

  /**
   * Authentication error handler
   */
  static authenticationError() {
    return (err, req, res, next) => {
      if (err.name === 'AuthenticationError' || err.name === 'JsonWebTokenError') {
        return res.status(err.statusCode || 401).json({
          success: false,
          error: err.code || 'AUTHENTICATION_ERROR',
          message: err.message,
          ...(err.details && { details: err.details })
        });
      }
      
      next(err);
    };
  }

  /**
   * Authorization error handler
   */
  static authorizationError() {
    return (err, req, res, next) => {
      if (err.name === 'AuthorizationError') {
        return res.status(err.statusCode || 403).json({
          success: false,
          error: err.code || 'FORBIDDEN',
          message: err.message,
          ...(err.details && { details: err.details })
        });
      }
      
      next(err);
    };
  }

  /**
   * Rate limit error handler
   */
  static rateLimitError() {
    return (err, req, res, next) => {
      if (err.name === 'RateLimitError') {
        return res.status(429).json({
          success: false,
          error: 'RATE_LIMIT_EXCEEDED',
          message: err.message,
          retryAfter: err.retryAfter
        });
      }
      
      next(err);
    };
  }

  /**
   * Business logic error handler
   */
  static businessError() {
    return (err, req, res, next) => {
      if (err.name === 'BusinessError') {
        return res.status(err.statusCode || 422).json({
          success: false,
          error: err.code || 'BUSINESS_ERROR',
          message: err.message,
          ...(err.details && { details: err.details })
        });
      }
      
      next(err);
    };
  }

  /**
   * Third-party service error handler
   */
  static serviceError() {
    return (err, req, res, next) => {
      if (err.name === 'ServiceError') {
        logger.error('Third-party service error', {
          service: err.service,
          error: err.message,
          path: req.path
        });
        
        return res.status(err.statusCode || 502).json({
          success: false,
          error: 'SERVICE_ERROR',
          message: err.message || 'External service unavailable',
          service: err.service
        });
      }
      
      next(err);
    };
  }

  /**
   * Fallback: Graceful degradation
   */
  static gracefulDegradation() {
    return (err, req, res, next) => {
      // If request is for critical endpoint, return degraded response
      if (req.path.startsWith('/api/critical')) {
        logger.warn('Graceful degradation activated', {
          path: req.path,
          error: err.message
        });
        
        return res.status(200).json({
          success: true,
          degraded: true,
          message: 'Service operating in degraded mode',
          data: null
        });
      }
      
      next(err);
    };
  }

  /**
   * Development error handler (detailed)
   */
  static developmentError() {
    return (err, req, res, next) => {
      if (config.NODE_ENV === 'development') {
        logger.error('Development error handler', {
          error: err.stack,
          path: req.path
        });
        
        return res.status(err.statusCode || 500).json({
          success: false,
          error: err.code || 'INTERNAL_ERROR',
          message: err.message,
          stack: err.stack,
          details: err.details,
          path: req.path,
          method: req.method,
          timestamp: new Date().toISOString()
        });
      }
      
      next(err);
    };
  }

  /**
   * Production error handler (sanitized)
   */
  static productionError() {
    return (err, req, res, next) => {
      // Log full error
      logger.error('Unhandled error', {
        error: err.message,
        stack: err.stack,
        path: req.path,
        method: req.method,
        userId: req.user?.id,
        requestId: req.requestId
      });
      
      // Send sanitized response
      return res.status(500).json({
        success: false,
        error: 'INTERNAL_SERVER_ERROR',
        message: 'An unexpected error occurred',
        requestId: req.requestId,
        timestamp: new Date().toISOString()
      });
    };
  }

  /**
   * Catch-all error handler
   */
  static final() {
    return (err, req, res, next) => {
      // Ensure headers aren't already sent
      if (res.headersSent) {
        return next(err);
      }
      
      const statusCode = err.statusCode || 500;
      
      // Default error response
      const errorResponse = {
        success: false,
        error: err.code || 'INTERNAL_ERROR',
        message: err.message || 'Internal server error',
        requestId: req.requestId,
        timestamp: new Date().toISOString()
      };
      
      // Add details in development
      if (config.NODE_ENV === 'development') {
        errorResponse.stack = err.stack;
        errorResponse.details = err.details;
      }
      
      res.status(statusCode).json(errorResponse);
    };
  }
}

module.exports = ErrorMiddleware;

3.4 Middleware Execution Flow Control

javascript
/**
 * ADVANCED FLOW CONTROL DEMONSTRATION
 */

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

// ==================== 1. MULTIPLE MIDDLEWARE CHAINS ====================

app.use('/chain',
  // Middleware 1: Authentication
  (req, res, next) => {
    console.log('1. Auth middleware');
    req.auth = { user: 'john', role: 'user' };
    next();
  },
  
  // Middleware 2: Validation
  (req, res, next) => {
    console.log('2. Validation middleware');
    if (!req.query.id) {
      return next(new Error('ID required'));
    }
    next();
  },
  
  // Middleware 3: Logging
  (req, res, next) => {
    console.log('3. Logging middleware');
    console.log(`User: ${req.auth.user}, ID: ${req.query.id}`);
    next();
  },
  
  // Final Handler
  (req, res) => {
    console.log('4. Final handler');
    res.json({ message: 'Chain completed' });
  }
);

// ==================== 2. CONDITIONAL MIDDLEWARE ====================

// Middleware that conditionally skips
app.use((req, res, next) => {
  if (req.path === '/health') {
    return next(); // Skip to next middleware
  }
  console.log('Processing:', req.path);
  next();
});

// ==================== 3. MULTIPLE MIDDLEWARE PATHS ====================

// Array of middleware
const authMiddleware = [
  (req, res, next) => {
    console.log('Auth check 1');
    next();
  },
  (req, res, next) => {
    console.log('Auth check 2');
    req.isAuthenticated = true;
    next();
  }
];

app.get('/secure', authMiddleware, (req, res) => {
  res.json({ message: 'Secure route' });
});

// ==================== 4. NEXT('ROUTE') SKIP REMAINING ====================

app.get('/skip',
  (req, res, next) => {
    console.log('Middleware 1');
    if (req.query.skip) {
      return next('route'); // Skip to next route, not middleware
    }
    next();
  },
  (req, res, next) => {
    console.log('Middleware 2 - This will be skipped');
    next();
  },
  (req, res) => {
    console.log('Handler 1');
    res.json({ message: 'Regular route' });
  }
);

app.get('/skip', (req, res) => {
  console.log('Handler 2 - Skipped route');
  res.json({ message: 'Skipped route' });
});

// ==================== 5. ASYNC MIDDLEWARE FLOW ====================

app.use(async (req, res, next) => {
  try {
    const start = Date.now();
    
    // Simulate async operation
    await new Promise(resolve => setTimeout(resolve, 100));
    
    console.log(`Async middleware took ${Date.now() - start}ms`);
    next();
  } catch (error) {
    next(error);
  }
});

// ==================== 6. DEPENDENT MIDDLEWARE ====================

// Middleware that depends on previous middleware
app.use((req, res, next) => {
  req.timestamp = Date.now();
  next();
});

app.use((req, res, next) => {
  req.requestId = `req_${req.timestamp}`;
  next();
});

app.use((req, res, next) => {
  console.log(`Request ID: ${req.requestId}`);
  next();
});

// ==================== 7. ERROR HANDLING FLOW ====================

app.get('/error-demo', (req, res, next) => {
  const error = new Error('Something went wrong');
  error.statusCode = 400;
  error.code = 'DEMO_ERROR';
  next(error); // Jump directly to error handler
});

// Error handling middleware (4 parameters)
app.use((err, req, res, next) => {
  console.error('Caught error:', err.message);
  
  // Can decide to pass to next error handler
  if (err.code === 'DEMO_ERROR') {
    return res.status(err.statusCode).json({
      error: err.code,
      message: err.message
    });
  }
  
  next(err); // Pass to next error handler
});

// ==================== 8. MULTIPLE ERROR HANDLERS ====================

// Specific error handler for validation errors
app.use((err, req, res, next) => {
  if (err.name === 'ValidationError') {
    return res.status(400).json({ error: 'Validation failed' });
  }
  next(err);
});

// Specific error handler for database errors
app.use((err, req, res, next) => {
  if (err.name === 'DatabaseError') {
    return res.status(503).json({ error: 'Database unavailable' });
  }
  next(err);
});

// Catch-all error handler
app.use((err, req, res, next) => {
  res.status(500).json({ error: 'Internal server error' });
});

3.5 Middleware Execution Order Visualization

javascript
/**
 * MIDDLEWARE EXECUTION ORDER VISUALIZATION
 * 
 * Request masuk → [Middleware 1] → [Middleware 2] → [Middleware 3] → [Handler]
 *                    ↓                ↓                ↓
 *                  (next)           (next)           (next)
 *                    ↓                ↓                ↓
 * Error ← [Error Handler 3] ← [Error Handler 2] ← [Error Handler 1]
 * 
 * Contoh konkret dengan timestamps:
 */

function demoMiddlewareOrder() {
  const express = require('express');
  const app = express();
  
  // Middleware 1: Logger
  app.use((req, res, next) => {
    console.log('1. Logger middleware - START');
    console.log(`   ${req.method} ${req.url}`);
    console.log('   Calling next()...');
    next();
    console.log('1. Logger middleware - END (response sent)');
  });
  
  // Middleware 2: Authenticator
  app.use((req, res, next) => {
    console.log('2. Auth middleware - START');
    req.user = { id: 1, name: 'John' };
    console.log('   User attached:', req.user);
    console.log('   Calling next()...');
    next();
    console.log('2. Auth middleware - END');
  });
  
  // Middleware 3: Validator
  app.use((req, res, next) => {
    console.log('3. Validator middleware - START');
    if (!req.query.id) {
      console.log('   Validation failed!');
      console.log('   Passing error to next()...');
      return next(new Error('ID is required'));
    }
    console.log('   Validation passed');
    console.log('   Calling next()...');
    next();
    console.log('3. Validator middleware - END');
  });
  
  // Route Handler
  app.get('/demo', (req, res) => {
    console.log('4. Route handler - START');
    console.log('   Processing request...');
    res.json({ 
      message: 'Success', 
      user: req.user,
      query: req.query 
    });
    console.log('4. Route handler - END (response sent)');
  });
  
  // Error Handler
  app.use((err, req, res, next) => {
    console.log('5. Error handler - START');
    console.log('   Error caught:', err.message);
    console.log('   Sending error response...');
    res.status(400).json({ error: err.message });
    console.log('5. Error handler - END');
  });
  
  return app;
}

/**
 * OUTPUT - SUCCESS CASE (http://localhost:3000/demo?id=123):
 * 
 * 1. Logger middleware - START
 *    GET /demo?id=123
 *    Calling next()...
 * 2. Auth middleware - START
 *    User attached: { id: 1, name: 'John' }
 *    Calling next()...
 * 3. Validator middleware - START
 *    Validation passed
 *    Calling next()...
 * 4. Route handler - START
 *    Processing request...
 * 4. Route handler - END (response sent)
 * 3. Validator middleware - END
 * 2. Auth middleware - END
 * 1. Logger middleware - END (response sent)
 * 
 * OUTPUT - ERROR CASE (http://localhost:3000/demo):
 * 
 * 1. Logger middleware - START
 *    GET /demo
 *    Calling next()...
 * 2. Auth middleware - START
 *    User attached: { id: 1, name: 'John' }
 *    Calling next()...
 * 3. Validator middleware - START
 *    Validation failed!
 *    Passing error to next()...
 * 5. Error handler - START
 *    Error caught: ID is required
 *    Sending error response...
 * 5. Error handler - END
 * 3. Validator middleware - END
 * 2. Auth middleware - END
 * 1. Logger middleware - END (response sent)
 */

3.6 Custom Middleware Patterns

1. Middleware Factory Pattern

javascript
/**
 * MIDDLEWARE FACTORY PATTERN
 * Create middleware with configuration
 */

function createRateLimiter(options = {}) {
  const {
    windowMs = 60000,
    max = 100,
    message = 'Too many requests'
  } = options;
  
  const requests = new Map();
  
  return function rateLimiter(req, res, next) {
    const ip = req.ip;
    const now = Date.now();
    
    // Get user's requests
    const userRequests = requests.get(ip) || [];
    
    // Clean old requests
    const recentRequests = userRequests.filter(time => now - time < windowMs);
    
    if (recentRequests.length >= max) {
      return res.status(429).json({
        error: 'RATE_LIMIT_EXCEEDED',
        message,
        retryAfter: Math.ceil(windowMs / 1000)
      });
    }
    
    // Add current request
    recentRequests.push(now);
    requests.set(ip, recentRequests);
    
    next();
  };
}

// Usage
app.use('/api', createRateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: 'API rate limit exceeded'
}));

app.use('/auth', createRateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Too many login attempts'
}));

2. Middleware Chain Builder

javascript
/**
 * MIDDLEWARE CHAIN BUILDER PATTERN
 * Build complex middleware chains fluently
 */

class MiddlewareChain {
  constructor() {
    this.middlewares = [];
  }
  
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  unless(condition, middleware) {
    return this.use((req, res, next) => {
      if (condition(req)) {
        return next();
      }
      return middleware(req, res, next);
    });
  }
  
  when(condition, middleware) {
    return this.use((req, res, next) => {
      if (condition(req)) {
        return middleware(req, res, next);
      }
      return next();
    });
  }
  
  build() {
    return this.middlewares;
  }
}

// Usage
const apiMiddleware = new MiddlewareChain()
  .use(rateLimiter)
  .use(compression())
  .when(req => req.method === 'POST', bodyParser.json())
  .unless(req => req.path === '/health', authMiddleware)
  .build();

app.use('/api', apiMiddleware);

3. Middleware Composition

javascript
/**
 * MIDDLEWARE COMPOSITION PATTERN
 * Compose multiple middleware into one
 */

function compose(middlewares) {
  return function(req, res, next) {
    let index = 0;
    
    function dispatch(i) {
      if (i === middlewares.length) {
        return next();
      }
      
      const middleware = middlewares[i];
      
      try {
        middleware(req, res, (err) => {
          if (err) {
            return next(err);
          }
          dispatch(i + 1);
        });
      } catch (err) {
        next(err);
      }
    }
    
    dispatch(0);
  };
}

// Usage
const authPipeline = compose([
  extractToken,
  verifyToken,
  attachUser,
  logAuth
]);

app.use('/secure', authPipeline);

3.7 Performance Optimization untuk Middleware (Lanjutan)

javascript
/**
 * PERFORMANCE OPTIMIZATION TECHNIQUES (LANJUTAN)
 */

// 3. Early bail-out for conditional middleware
app.use((req, res, next) => {
  // Skip middleware untuk health check endpoints
  if (req.path === '/health' || req.path === '/ready') {
    return next();
  }
  
  // Skip untuk OPTIONS requests (preflight CORS)
  if (req.method === 'OPTIONS') {
    return next();
  }
  
  // Skip untuk static files
  if (req.path.match(/\.(css|js|png|jpg|svg|ico)$/)) {
    return next();
  }
  
  // Baru jalankan middleware yang berat
  console.log('Processing expensive middleware...');
  next();
});

// 4. Lazy initialization middleware
let heavyMiddleware = null;

app.use((req, res, next) => {
  if (!heavyMiddleware) {
    console.log('Initializing heavy middleware...');
    heavyMiddleware = createExpensiveMiddleware();
  }
  
  return heavyMiddleware(req, res, next);
});

// 5. Throttle middleware untuk non-critical operations
const pendingOperations = new Set();

app.use((req, res, next) => {
  // Analytics tracking - jangan block request
  setImmediate(() => {
    if (!pendingOperations.has(req.id)) {
      pendingOperations.add(req.id);
      trackAnalytics(req).finally(() => {
        pendingOperations.delete(req.id);
      });
    }
  });
  
  next();
});

// 6. Batch processing middleware
const batchBuffer = [];
const BATCH_SIZE = 100;
const BATCH_TIMEOUT = 1000; // 1 second

setInterval(() => {
  if (batchBuffer.length > 0) {
    const batch = [...batchBuffer];
    batchBuffer.length = 0;
    processBatchLogs(batch).catch(console.error);
  }
}, BATCH_TIMEOUT);

app.use((req, res, next) => {
  // Queue log entries instead of writing immediately
  batchBuffer.push({
    timestamp: new Date(),
    method: req.method,
    path: req.path,
    user: req.user?.id
  });
  
  next();
});

3.8 Testing Middleware Secara Komprehensif

tests/middleware/auth.middleware.test.js

javascript
/**
 * UNIT TESTING MIDDLEWARE
 */

const request = require('supertest');
const express = require('express');
const AuthMiddleware = require('../../src/middleware/auth.middleware');
const jwt = require('jsonwebtoken');

describe('Auth Middleware', () => {
  let app;
  
  beforeEach(() => {
    app = express();
    jest.clearAllMocks();
  });

  describe('authenticate', () => {
    it('should return 401 if no token provided', async () => {
      app.use('/test', AuthMiddleware.authenticate());
      app.get('/test', (req, res) => res.json({ success: true }));

      const response = await request(app)
        .get('/test')
        .expect(401);

      expect(response.body.error).toBe('NO_TOKEN');
    });

    it('should return 401 if invalid token', async () => {
      app.use('/test', AuthMiddleware.authenticate());
      app.get('/test', (req, res) => res.json({ success: true }));

      const response = await request(app)
        .get('/test')
        .set('Authorization', 'Bearer invalid.token.here')
        .expect(401);

      expect(response.body.error).toBe('INVALID_TOKEN');
    });

    it('should attach user to request if valid token', async () => {
      const token = jwt.sign(
        { sub: 1, email: 'test@test.com', role: 'user' },
        process.env.JWT_SECRET || 'secret'
      );

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

      const response = await request(app)
        .get('/test')
        .set('Authorization', `Bearer ${token}`)
        .expect(200);

      expect(response.body.user).toBeDefined();
      expect(response.body.user.id).toBe(1);
      expect(response.body.user.email).toBe('test@test.com');
    });
  });

  describe('authorize', () => {
    it('should return 403 if user role not authorized', async () => {
      // Mock user yang sudah terautentikasi
      app.use((req, res, next) => {
        req.user = { id: 1, role: 'user' };
        next();
      });

      app.use('/admin', AuthMiddleware.authorize('admin'));
      app.get('/admin', (req, res) => res.json({ success: true }));

      const response = await request(app)
        .get('/admin')
        .expect(403);

      expect(response.body.error).toBe('FORBIDDEN');
    });

    it('should allow access if user has required role', async () => {
      app.use((req, res, next) => {
        req.user = { id: 1, role: 'admin' };
        next();
      });

      app.use('/admin', AuthMiddleware.authorize('admin'));
      app.get('/admin', (req, res) => res.json({ success: true }));

      const response = await request(app)
        .get('/admin')
        .expect(200);

      expect(response.body.success).toBe(true);
    });
  });

  describe('verifyOwnership', () => {
    it('should allow admin to access any resource', async () => {
      const mockResourceGetter = jest.fn().mockResolvedValue({
        id: 1,
        userId: 2,
        title: 'Test Post'
      });

      app.use((req, res, next) => {
        req.user = { id: 1, role: 'admin' };
        req.params = { id: '1' };
        next();
      });

      app.use('/posts/:id', 
        AuthMiddleware.verifyOwnership(mockResourceGetter)
      );

      app.get('/posts/:id', (req, res) => {
        res.json({ success: true });
      });

      await request(app)
        .get('/posts/1')
        .expect(200);

      expect(mockResourceGetter).toHaveBeenCalled();
    });

    it('should deny access if user is not owner', async () => {
      const mockResourceGetter = jest.fn().mockResolvedValue({
        id: 1,
        userId: 2,
        title: 'Test Post'
      });

      app.use((req, res, next) => {
        req.user = { id: 3, role: 'user' };
        req.params = { id: '1' };
        next();
      });

      app.use('/posts/:id', 
        AuthMiddleware.verifyOwnership(mockResourceGetter)
      );

      app.get('/posts/:id', (req, res) => {
        res.json({ success: true });
      });

      const response = await request(app)
        .get('/posts/1')
        .expect(403);

      expect(response.body.error).toBe('NOT_OWNER');
    });
  });
});

tests/middleware/rate-limit.middleware.test.js

javascript
/**
 * TESTING RATE LIMITING MIDDLEWARE
 */

const request = require('supertest');
const express = require('express');
const RateLimitMiddleware = require('../../src/middleware/rate-limit.middleware');

describe('Rate Limit Middleware', () => {
  let app;

  beforeEach(() => {
    app = express();
  });

  describe('standard rate limiter', () => {
    it('should allow requests under the limit', async () => {
      app.use('/test', RateLimitMiddleware.standard());
      app.get('/test', (req, res) => res.json({ success: true }));

      for (let i = 0; i < 5; i++) {
        await request(app)
          .get('/test')
          .expect(200);
      }
    });

    it('should block requests over the limit', async () => {
      // Override untuk testing
      const limiter = RateLimitMiddleware.standard();
      limiter.windowMs = 1000; // 1 second
      limiter.max = 3;

      app.use('/test', limiter);
      app.get('/test', (req, res) => res.json({ success: true }));

      // 3 requests should pass
      for (let i = 0; i < 3; i++) {
        await request(app)
          .get('/test')
          .expect(200);
      }

      // 4th request should be blocked
      const response = await request(app)
        .get('/test')
        .expect(429);

      expect(response.body.error).toBe('RATE_LIMIT_EXCEEDED');
    });

    it('should skip rate limiting for admin users', async () => {
      const limiter = RateLimitMiddleware.standard();
      limiter.max = 1;

      app.use((req, res, next) => {
        req.user = { role: 'admin' };
        next();
      });

      app.use('/test', limiter);
      app.get('/test', (req, res) => res.json({ success: true }));

      // Admin should be able to exceed limit
      for (let i = 0; i < 5; i++) {
        await request(app)
          .get('/test')
          .expect(200);
      }
    });
  });

  describe('strict rate limiter', () => {
    it('should not count successful requests', async () => {
      const limiter = RateLimitMiddleware.strict();
      limiter.windowMs = 1000;
      limiter.max = 2;

      app.use('/auth', limiter);
      app.post('/auth/login', (req, res) => res.json({ success: true }));

      // Successful logins shouldn't count
      for (let i = 0; i < 5; i++) {
        await request(app)
          .post('/auth/login')
          .expect(200);
      }

      // Failed attempts should be limited
      app.post('/auth/login', (req, res) => res.status(401).json({ error: 'Invalid' }));

      for (let i = 0; i < 3; i++) {
        if (i < 2) {
          await request(app)
            .post('/auth/login')
            .expect(401);
        } else {
          const response = await request(app)
            .post('/auth/login')
            .expect(429);
          
          expect(response.body.error).toBe('RATE_LIMIT_EXCEEDED');
        }
      }
    });
  });

  describe('concurrent request limiter', () => {
    it('should limit concurrent requests', async () => {
      const limiter = RateLimitMiddleware.concurrent(2);
      
      app.use('/test', limiter);
      app.get('/test', async (req, res) => {
        // Simulate long processing
        await new Promise(resolve => setTimeout(resolve, 100));
        res.json({ success: true });
      });

      // Send 3 concurrent requests
      const promises = [];
      for (let i = 0; i < 3; i++) {
        promises.push(request(app).get('/test'));
      }

      const results = await Promise.all(promises);
      
      // 2 should succeed, 1 should be rate limited
      const successCount = results.filter(r => r.statusCode === 200).length;
      const limitedCount = results.filter(r => r.statusCode === 429).length;

      expect(successCount).toBe(2);
      expect(limitedCount).toBe(1);
    });
  });
});

3.9 Middleware Design Patterns di Production

1. Middleware with Dependency Injection

javascript
/**
 * DEPENDENCY INJECTION PATTERN
 * Untuk testing dan modularity yang lebih baik
 */

class MiddlewareContainer {
  constructor() {
    this.services = new Map();
  }

  register(name, service) {
    this.services.set(name, service);
  }

  get(name) {
    return this.services.get(name);
  }
}

// Factory function dengan dependency injection
function createAuthMiddleware(container) {
  const userService = container.get('userService');
  const tokenService = container.get('tokenService');
  const logger = container.get('logger');

  return async (req, res, next) => {
    try {
      const token = req.headers.authorization?.split(' ')[1];
      
      if (!token) {
        throw new Error('No token provided');
      }

      const decoded = await tokenService.verify(token);
      const user = await userService.findById(decoded.sub);
      
      req.user = user;
      logger.info(`User ${user.id} authenticated`);
      
      next();
    } catch (error) {
      logger.error('Auth failed', { error: error.message });
      next(error);
    }
  };
}

// Penggunaan
const container = new MiddlewareContainer();
container.register('userService', new UserService());
container.register('tokenService', new TokenService());
container.register('logger', logger);

const authMiddleware = createAuthMiddleware(container);
app.use('/api', authMiddleware);

2. Middleware Registry Pattern

javascript
/**
 * MIDDLEWARE REGISTRY PATTERN
 * Untuk manajemen middleware terpusat
 */

class MiddlewareRegistry {
  constructor() {
    this.middlewares = new Map();
    this.groups = new Map();
  }

  // Register single middleware
  register(name, middleware, options = {}) {
    this.middlewares.set(name, {
      fn: middleware,
      options,
      enabled: options.enabled !== false
    });
  }

  // Register group of middleware
  group(name, middlewares) {
    this.groups.set(name, middlewares);
  }

  // Get middleware by name
  get(name) {
    const middleware = this.middlewares.get(name);
    if (!middleware || !middleware.enabled) {
      return null;
    }
    return middleware.fn;
  }

  // Get group of middleware
  getGroup(name) {
    const group = this.groups.get(name);
    if (!group) return [];
    
    return group
      .map(mw => this.get(mw))
      .filter(Boolean);
  }

  // Enable/disable middleware
  enable(name) {
    const mw = this.middlewares.get(name);
    if (mw) mw.enabled = true;
  }

  disable(name) {
    const mw = this.middlewares.get(name);
    if (mw) mw.enabled = false;
  }
}

// Setup registry
const registry = new MiddlewareRegistry();

// Register middleware
registry.register('logger', morgan('combined'));
registry.register('cors', cors());
registry.register('helmet', helmet());
registry.register('rateLimit', rateLimit());
registry.register('auth', authMiddleware);
registry.register('adminAuth', adminAuthMiddleware);

// Define groups
registry.group('api', ['logger', 'cors', 'helmet', 'rateLimit']);
registry.group('public', ['logger', 'cors']);
registry.group('protected', ['auth', 'logger']);
registry.group('admin', ['auth', 'adminAuth', 'logger']);

// Usage in routes
app.use('/api', registry.getGroup('api'));
app.use('/public', registry.getGroup('public'));
app.use('/protected', registry.getGroup('protected'));
app.use('/admin', registry.getGroup('admin'));

3. Middleware Stack Pattern

javascript
/**
 * MIDDLEWARE STACK PATTERN
 * Untuk routing yang kompleks
 */

class MiddlewareStack {
  constructor() {
    this.stack = [];
  }

  // Add middleware to stack
  use(middleware) {
    this.stack.push(middleware);
    return this;
  }

  // Add conditional middleware
  when(condition, middleware) {
    return this.use((req, res, next) => {
      if (condition(req)) {
        return middleware(req, res, next);
      }
      return next();
    });
  }

  // Skip middleware for certain paths
  except(paths, middleware) {
    const skipPaths = Array.isArray(paths) ? paths : [paths];
    
    return this.use((req, res, next) => {
      if (skipPaths.some(path => req.path.startsWith(path))) {
        return next();
      }
      return middleware(req, res, next);
    });
  }

  // Add error handler
  catch(errorHandler) {
    return this.use((err, req, res, next) => {
      return errorHandler(err, req, res, next);
    });
  }

  // Execute stack
  execute(req, res, finalHandler) {
    let index = 0;
    
    const next = (error) => {
      if (error) {
        const errorHandler = this.stack.find(mw => mw.length === 4);
        if (errorHandler) {
          return errorHandler(error, req, res, next);
        }
        return finalHandler(error, req, res);
      }

      if (index >= this.stack.length) {
        return finalHandler(null, req, res);
      }

      const middleware = this.stack[index++];
      
      try {
        if (middleware.length === 4) {
          // Skip error handler
          return next();
        }
        
        middleware(req, res, next);
      } catch (err) {
        next(err);
      }
    };

    next();
  }
}

// Usage
const apiStack = new MiddlewareStack()
  .use(logger)
  .use(cors())
  .use(helmet())
  .when(req => req.method === 'POST', bodyParser.json())
  .except('/health', rateLimit())
  .use(authMiddleware)
  .use(validationMiddleware)
  .catch(errorHandler);

// Apply to route
app.all('/api/*', (req, res) => {
  apiStack.execute(req, res, (err, req, res) => {
    if (err) {
      return res.status(500).json({ error: err.message });
    }
    res.json({ message: 'Success' });
  });
});

3.10 Production Readiness Checklist

javascript
/**
 * PRODUCTION READINESS CHECKLIST UNTUK MIDDLEWARE
 */

const productionMiddleware = [
  {
    name: 'Security Headers',
    middleware: helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          scriptSrc: ["'self'"],
          styleSrc: ["'self'"],
          imgSrc: ["'self'", 'data:', 'https:'],
        },
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      }
    }),
    required: true
  },
  {
    name: 'CORS Protection',
    middleware: cors({
      origin: process.env.CORS_ORIGIN?.split(',') || [],
      credentials: true,
      maxAge: 86400
    }),
    required: true
  },
  {
    name: 'Rate Limiting',
    middleware: rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 100,
      message: 'Too many requests',
      standardHeaders: true,
      legacyHeaders: false
    }),
    required: true
  },
  {
    name: 'Request Size Limit',
    middleware: express.json({ limit: '10mb' }),
    required: true
  },
  {
    name: 'Compression',
    middleware: compression(),
    required: true
  },
  {
    name: 'Request Logging',
    middleware: morgan('combined', {
      skip: (req, res) => req.path === '/health'
    }),
    required: true
  },
  {
    name: 'XSS Protection',
    middleware: (req, res, next) => {
      res.setHeader('X-XSS-Protection', '1; mode=block');
      next();
    },
    required: true
  },
  {
    name: 'SQL Injection Prevention',
    middleware: (req, res, next) => {
      const sqlPattern = /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER)\b)|('--)|(;\s*$)/i;
      
      for (const key in req.query) {
        if (sqlPattern.test(req.query[key])) {
          return res.status(400).json({ error: 'Invalid input' });
        }
      }
      next();
    },
    required: process.env.DB_TYPE === 'sql'
  },
  {
    name: 'API Versioning',
    middleware: (req, res, next) => {
      req.apiVersion = req.headers['accept-version'] || 'v1';
      next();
    },
    required: true
  },
  {
    name: 'Request ID',
    middleware: (req, res, next) => {
      req.id = req.headers['x-request-id'] || uuid.v4();
      res.setHeader('X-Request-ID', req.id);
      next();
    },
    required: true
  },
  {
    name: 'Response Time',
    middleware: (req, res, next) => {
      const start = Date.now();
      res.on('finish', () => {
        const duration = Date.now() - start;
        res.setHeader('X-Response-Time', `${duration}ms`);
      });
      next();
    },
    required: true
  }
];

// Apply production middleware
productionMiddleware.forEach(({ middleware, required }) => {
  if (required) {
    app.use(middleware);
  }
});

// Production error handler
app.use((err, req, res, next) => {
  // Log error
  console.error({
    timestamp: new Date().toISOString(),
    error: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    requestId: req.id,
    user: req.user?.id
  });

  // Don't expose error details in production
  res.status(err.status || 500).json({
    error: process.env.NODE_ENV === 'production' 
      ? 'Internal server error' 
      : err.message,
    requestId: req.id
  });
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received. Shutting down gracefully...');
  
  // Stop accepting new requests
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
  
  // Force close after 30s
  setTimeout(() => {
    console.error('Force shutdown');
    process.exit(1);
  }, 30000);
});

3.11 Latihan Praktikum

Exercise 1: Custom Caching Middleware

javascript
/**
 * TODO: Implement caching middleware dengan fitur:
 * - Cache GET requests
 * - TTL (Time To Live) configurable
 * - Cache invalidation on PUT/POST/DELETE
 * - Redis support untuk distributed cache
 * - Cache statistics (hit/miss ratio)
 */

class CacheMiddleware {
  constructor(options = {}) {
    this.ttl = options.ttl || 60; // seconds
    this.cache = new Map();
    this.stats = { hits: 0, misses: 0 };
  }

  middleware() {
    // Implementasi
  }

  invalidate(pattern) {
    // Implementasi
  }

  getStats() {
    // Implementasi
  }
}

Exercise 2: Request Validation Pipeline

javascript
/**
 * TODO: Build validation pipeline dengan fitur:
 * - Chainable validation rules
 * - Custom error messages
 * - Async validation support
 * - Nested object validation
 * - Array validation
 */

class ValidationPipeline {
  constructor() {
    this.rules = [];
  }

  field(name) {
    // Implementasi
    return this;
  }

  required(message) {
    // Implementasi
    return this;
  }

  email(message) {
    // Implementasi
    return this;
  }

  min(length, message) {
    // Implementasi
    return this;
  }

  max(length, message) {
    // Implementasi
    return this;
  }

  pattern(regex, message) {
    // Implementasi
    return this;
  }

  custom(validator, message) {
    // Implementasi
    return this;
  }

  async validate(data) {
    // Implementasi
  }
}

Exercise 3: Multi-tenant Middleware

javascript
/**
 * TODO: Implement multi-tenant middleware dengan fitur:
 * - Tenant identification (subdomain, header, path)
 * - Tenant isolation (database, cache)
 * - Tenant-specific configuration
 * - Tenant metrics
 * - Tenant rate limiting
 */

class TenantMiddleware {
  identifyTenant(req) {
    // Implementasi
  }

  middleware() {
    return async (req, res, next) => {
      // Implementasi
    };
  }

  isolateDatabase(tenant) {
    // Implementasi
  }
}

Exercise 4: Audit Trail Middleware

javascript
/**
 * TODO: Build audit trail middleware dengan fitur:
 * - Track all CRUD operations
 * - Before/after state capture
 * - User attribution
 * - IP tracking
 * - Searchable audit log
 */

class AuditMiddleware {
  log(operation, before, after) {
    // Implementasi
  }

  middleware() {
    return async (req, res, next) => {
      // Implementasi
    };
  }

  search(criteria) {
    // Implementasi
  }
}

4. Daftar Pustaka

  1. Express.js Documentation (n.d). Advanced Middleware. https://expressjs.com
  2. Medium (2025). Building Rock-Solid Express.js Middleware: A Guide that Actually Works in Production. https://medium.com/@almatins
  3. Biztech Academy (2024). Panduan Lengkap : Cara Belajar Node.js dari Dasar Sampai Mahir. https://biztechacademy.id

Posting Komentar

0 Komentar