Files
pos-system/.agent/rules/middleware-patterns.md

8.3 KiB

trigger
trigger
always_on

Middleware Patterns

When to Use This Skill

Use this skill when:

  • Creating custom Express middleware
  • Organizing middleware chains and ordering
  • Implementing authentication/authorization middleware
  • Creating request/response transformation middleware
  • Handling cross-cutting concerns (logging, metrics, validation)
  • Implementing async middleware patterns
  • Testing middleware implementations

Core Concepts

Middleware Function Signature

Express middleware functions have this signature:

(req: Request, res: Response, next: NextFunction) => void | Promise<void>

Middleware Types

  1. Application-level: Applied to all routes (app.use())
  2. Router-level: Applied to specific routes (router.use())
  3. Route-level: Applied to specific route handlers

Middleware Execution Order

Critical: Middleware order matters! Execution flows top-to-bottom:

Request → Middleware 1 → Middleware 2 → ... → Route Handler → Response

Patterns

Middleware Chain Order

Standard middleware order in GoodGo services:

// 1. Security (Helmet, CORS)
app.use(helmet());
app.use(cors({ ... }));

// 2. Rate Limiting
app.use('/api', rateLimitMiddleware);

// 3. Correlation ID (early for tracing)
app.use(correlationMiddleware());

// 4. Body Parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// 5. Request Logging
app.use(loggerMiddleware);

// 6. Metrics
app.use(metricsMiddleware);

// 7. Routes
app.use(createRouter());

// 8. Error Handling (ALWAYS LAST)
app.use(notFoundHandler);
app.use(errorHandler);

Correlation Middleware Pattern

Adds correlation ID for request tracing:

export const correlationMiddleware = () => {
  return (req: Request, res: Response, next: NextFunction) => {
    const correlationId = req.headers['x-correlation-id'] || generateId();
    req.correlationId = correlationId;
    res.setHeader('x-correlation-id', correlationId);
    next();
  };
};

Authentication Middleware Pattern

Verifies JWT tokens and attaches user to request:

export const authenticate = () => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const token = extractToken(req);
      if (!token) {
        return res.status(401).json({ error: 'Unauthorized' });
      }
      
      const payload = await jwtService.verify(token);
      req.user = payload;
      next();
    } catch (error) {
      return res.status(401).json({ error: 'Invalid token' });
    }
  };
};

Validation Middleware Pattern

Validates request data using Zod:

export const validateDto = (schema: AnyZodObject, property: 'body' | 'query' | 'params' = 'body') => {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      const validatedData = schema.parse(req[property]);
      (req as any)[property] = validatedData;
      next();
    } catch (error) {
      if (error instanceof ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            details: error.errors,
          },
        });
      }
      next(error);
    }
  };
};

Conditional Middleware

Apply middleware conditionally:

const conditionalAuth = (options: { optional?: boolean } = {}) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const token = extractToken(req);
      if (token) {
        const payload = await jwtService.verify(token);
        req.user = payload;
      } else if (!options.optional) {
        return res.status(401).json({ error: 'Unauthorized' });
      }
      next();
    } catch (error) {
      if (!options.optional) {
        return res.status(401).json({ error: 'Invalid token' });
      }
      next();
    }
  };
};

Async Middleware Pattern

Handle async operations properly:

export const asyncMiddleware = (fn: Function) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Usage
app.get('/users', asyncMiddleware(async (req, res) => {
  const users = await userService.findAll();
  res.json({ success: true, data: users });
}));

Request/Response Transformation

Transform request or response data:

export const transformResponse = () => {
  return (req: Request, res: Response, next: NextFunction) => {
    const originalJson = res.json.bind(res);

    res.json = function(data: unknown) {
      const transformed = {
        success: true,
        data,
        timestamp: new Date().toISOString(),
      };
      return originalJson(transformed);
    };

    next();
  };
};

Logging Middleware Pattern

Log request details:

export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
  const startTime = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    logger.info('Request completed', {
      method: req.method,
      url: req.url,
      statusCode: res.statusCode,
      duration,
      correlationId: req.correlationId,
    });
  });
  
  next();
};

Best Practices

  1. Order Matters: Place middleware in correct order (security → correlation → parsing → logging → routes → errors)
  2. Error Handling: Always handle errors and call next(error) for error middleware
  3. Async Support: Wrap async middleware properly to catch promise rejections
  4. Early Returns: Use early returns for validation failures (don't call next())
  5. Request Extension: Use TypeScript declaration merging to extend Request type
  6. Conditional Logic: Use middleware factories for conditional middleware
  7. Reusability: Create reusable middleware functions
  8. Performance: Keep middleware lightweight, avoid heavy operations

Common Mistakes

  1. Wrong Order: Placing middleware in incorrect order (e.g., error handler before routes)
  2. Not Calling Next: Forgetting to call next() or next(error)
  3. Async Errors: Not handling promise rejections in async middleware
  4. Early Return Issues: Calling next() after sending response
  5. Type Safety: Not extending Express Request type properly
  6. Performance: Doing heavy operations in middleware

Troubleshooting

Middleware Not Executing

Problem: Middleware not being called Solution: Check middleware order, ensure it's added before routes. Verify next() is called.

Async Errors Not Caught

Problem: Unhandled promise rejections in async middleware Solution: Use asyncHandler wrapper or wrap async code in try-catch with next(error).

Quick Reference

Middleware Order (Critical):

1. Security (helmet, cors)
2. Rate Limiting
3. Correlation ID
4. Body Parsing (json, urlencoded)
5. Request Logging
6. Metrics
7. Routes
8. Error Handling (LAST)
Middleware Type Signature When to Use
Standard (req, res, next) => void Sync operations
Async async (req, res, next) => Promise<void> Async operations
Error (err, req, res, next) => void Error handling

Common Patterns:

// Async wrapper
const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

// Conditional middleware
const conditionalMiddleware = (condition, middleware) =>
  condition ? middleware : (req, res, next) => next();

// Factory pattern
const authenticate = (options = {}) => async (req, res, next) => { /* ... */ };

Essential Imports:

import { Request, Response, NextFunction } from 'express';
import { z, ZodError } from 'zod';
import { logger } from '@goodgo/logger';

Resources