Files
pos-system/docs/en/skills/middleware-patterns.md
Ho Ngoc Hai 9b6c585f57 Enhance documentation structure and improve bilingual support across skills
- Updated skill documentation files to include structured metadata for better organization.
- Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese.
- Refined sections on usage, best practices, and related skills to ensure consistency across all documentation.
- Improved formatting and removed outdated references to streamline the documentation experience.
- Added best practices checklists to relevant skills for better usability and adherence to standards.
2026-01-01 07:35:44 +07:00

7.3 KiB

name, description
name description
middleware-patterns Express middleware patterns and best practices for GoodGo microservices. Use when creating custom middleware, organizing middleware chains, handling request/response transformation, or implementing cross-cutting concerns.

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: any) {
      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).

Resources