315 lines
8.3 KiB
Markdown
315 lines
8.3 KiB
Markdown
---
|
|
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:
|
|
|
|
```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
|
|
```
|
|
|
|
## 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' });
|
|
}
|
|
};
|
|
};
|
|
```
|
|
|
|
### 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);
|
|
}
|
|
};
|
|
};
|
|
```
|
|
|
|
### 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 });
|
|
}));
|
|
```
|
|
|
|
### 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: unknown) {
|
|
const transformed = {
|
|
success: true,
|
|
data,
|
|
timestamp: new Date().toISOString(),
|
|
};
|
|
return originalJson(transformed);
|
|
};
|
|
|
|
next();
|
|
};
|
|
};
|
|
```
|
|
|
|
### 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();
|
|
};
|
|
```
|
|
|
|
## 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:**
|
|
```typescript
|
|
// 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:**
|
|
```typescript
|
|
import { Request, Response, NextFunction } from 'express';
|
|
import { z, ZodError } from 'zod';
|
|
import { logger } from '@goodgo/logger';
|
|
```
|
|
|
|
## Resources
|
|
|
|
- [Express Middleware](https://expressjs.com/en/guide/writing-middleware.html) - Express middleware guide
|
|
- [Error Handling](../error-handling-patterns/SKILL.md) - Error middleware patterns
|
|
- [Security](../security/SKILL.md) - Auth middleware patterns
|
|
- [API Design](../api-design/SKILL.md) - Request/response patterns
|
|
- [Resilience Patterns](../resilience-patterns/SKILL.md) - Circuit breaker middleware
|
|
- [Observability & Monitoring](../observability-monitoring/SKILL.md) - Logging middleware
|
|
- [Project Rules](../project-rules/SKILL.md) - GoodGo standards
|