- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes. - Introduced configuration loading flow in configuration-management skill to clarify the configuration process. - Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers. - Enhanced various skills with additional diagrams to improve understanding of complex concepts. These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
414 lines
12 KiB
Markdown
414 lines
12 KiB
Markdown
---
|
|
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<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
|
|
```
|
|
|
|
The following diagram illustrates the complete middleware chain flow in GoodGo services:
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
Start([HTTP Request]) --> Security["Security Middleware<br/>Helmet, CORS"]
|
|
Security --> RateLimit["Rate Limiting<br/>Middleware"]
|
|
RateLimit --> Correlation["Correlation ID<br/>Middleware"]
|
|
Correlation --> BodyParsing["Body Parsing<br/>JSON, URLEncoded, Cookies"]
|
|
BodyParsing --> Logging["Request Logging<br/>Middleware"]
|
|
Logging --> Metrics["Metrics Collection<br/>Middleware"]
|
|
Metrics --> Routes["Route Handlers"]
|
|
Routes -->|Success| Response([HTTP Response])
|
|
Routes -->|Error| ErrorHandler["Error Handler<br/>Middleware"]
|
|
Routes -->|Not Found| NotFound["Not Found<br/>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', {<br/>method, url, statusCode,<br/>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
|