--- name: middleware-patterns description: 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: ```typescript (req: Request, res: Response, next: NextFunction) => void | Promise ``` ### 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 ``` The following diagram illustrates the complete middleware chain flow in GoodGo services: ```mermaid flowchart TD Start([HTTP Request]) --> Security["Security Middleware
Helmet, CORS"] Security --> RateLimit["Rate Limiting
Middleware"] RateLimit --> Correlation["Correlation ID
Middleware"] Correlation --> BodyParsing["Body Parsing
JSON, URLEncoded, Cookies"] BodyParsing --> Logging["Request Logging
Middleware"] Logging --> Metrics["Metrics Collection
Middleware"] Metrics --> Routes["Route Handlers"] Routes -->|Success| Response([HTTP Response]) Routes -->|Error| ErrorHandler["Error Handler
Middleware"] Routes -->|Not Found| NotFound["Not Found
Handler"] ErrorHandler --> Response NotFound --> Response ``` ## Patterns ### Middleware Chain Order Standard middleware order in GoodGo services: ```typescript // 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: ```typescript 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: ```typescript 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' }); } }; }; ``` The following sequence diagram illustrates the authentication middleware flow: ```mermaid sequenceDiagram participant Client participant AuthMW as Authentication Middleware participant JWTService as JWT Service participant RouteHandler as Route Handler Client->>AuthMW: HTTP Request with Token AuthMW->>AuthMW: Extract token from headers alt Token exists AuthMW->>JWTService: Verify token alt Token valid JWTService-->>AuthMW: Payload (user data) AuthMW->>AuthMW: Attach user to req.user AuthMW->>RouteHandler: next() - Continue RouteHandler->>Client: HTTP Response (200) else Token invalid JWTService-->>AuthMW: Verification error AuthMW->>Client: HTTP Response (401 Unauthorized) end else No token AuthMW->>Client: HTTP Response (401 Unauthorized) end ``` ### Validation Middleware Pattern Validates request data using Zod: ```typescript 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); } }; }; ``` The following sequence diagram shows the validation middleware flow: ```mermaid sequenceDiagram participant Client participant ValidateMW as Validation Middleware participant ZodSchema as Zod Schema participant RouteHandler as Route Handler Client->>ValidateMW: HTTP Request with Data ValidateMW->>ValidateMW: Extract data from req[property] ValidateMW->>ZodSchema: schema.parse(data) alt Validation successful ZodSchema-->>ValidateMW: Validated data ValidateMW->>ValidateMW: Replace req[property] with validated data ValidateMW->>RouteHandler: next() - Continue RouteHandler->>Client: HTTP Response (200) else Validation failed ZodSchema-->>ValidateMW: ZodError with details ValidateMW->>ValidateMW: Format error response ValidateMW->>Client: HTTP Response (400 Validation Error) end ``` ### Conditional Middleware Apply middleware conditionally: ```typescript 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: ```typescript 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 }); })); ``` The following sequence diagram illustrates async middleware error handling: ```mermaid sequenceDiagram participant Client participant AsyncMW as Async Middleware Wrapper participant AsyncHandler as Async Route Handler participant ErrorHandler as Error Handler Client->>AsyncMW: HTTP Request AsyncMW->>AsyncHandler: Execute async function alt Async operation succeeds AsyncHandler->>AsyncHandler: Process request AsyncHandler->>Client: HTTP Response (200) else Async operation fails AsyncHandler-->>AsyncMW: Promise rejection (Error) AsyncMW->>ErrorHandler: next(error) ErrorHandler->>ErrorHandler: Format error response ErrorHandler->>Client: HTTP Response (500 Error) end ``` ### Request/Response Transformation Transform request or response data: ```typescript 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(); }; }; ``` The following sequence diagram shows how request and response transformation middleware works: ```mermaid sequenceDiagram participant Client participant TransformMW as Transform Middleware participant RouteHandler as Route Handler participant Response as Response Object Client->>TransformMW: HTTP Request Note over TransformMW: Intercept res.json() TransformMW->>TransformMW: Store original res.json TransformMW->>TransformMW: Override res.json with transformation logic TransformMW->>RouteHandler: next() - Continue chain RouteHandler->>RouteHandler: Process request, Generate data RouteHandler->>Response: res.json(rawData) Note over Response: Transformed res.json executes Response->>Response: Wrap data: success, data, timestamp Response->>Client: HTTP Response (transformed) ``` ### Logging Middleware Pattern Log request details: ```typescript 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(); }; ``` The following sequence diagram illustrates how logging middleware tracks request lifecycle: ```mermaid sequenceDiagram participant Client participant LogMW as Logging Middleware participant RouteHandler as Route Handler participant Logger as Logger Service participant Response as Response Object Client->>LogMW: HTTP Request LogMW->>LogMW: Record startTime = Date.now() LogMW->>RouteHandler: next() - Continue chain RouteHandler->>RouteHandler: Process request RouteHandler->>Response: res.json(data) or res.send() Response->>Response: Set statusCode, send response Response->>Response: Emit 'finish' event Response->>LogMW: 'finish' event triggered LogMW->>LogMW: Calculate duration = Date.now() - startTime LogMW->>Logger: logger.info('Request completed', {
method, url, statusCode,
duration, correlationId}) Logger->>Logger: Write structured log entry Response->>Client: HTTP Response ``` ## 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 - [Correlation Middleware](../../services/iam-service/src/middlewares/correlation.middleware.ts) - [Auth Middleware](../../services/iam-service/src/middlewares/auth.middleware.ts) - [Validation Middleware](../../services/iam-service/src/middlewares/validation.middleware.ts) - [Error Handling](../error-handling-patterns/SKILL.md) - Error middleware patterns