Initial commit: The Ultimate Antigravity Skills Collection (58 Skills)
This commit is contained in:
213
skills/backend-dev-guidelines/resources/middleware-guide.md
Normal file
213
skills/backend-dev-guidelines/resources/middleware-guide.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# Middleware Guide - Express Middleware Patterns
|
||||
|
||||
Complete guide to creating and using middleware in backend microservices.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Authentication Middleware](#authentication-middleware)
|
||||
- [Audit Middleware with AsyncLocalStorage](#audit-middleware-with-asynclocalstorage)
|
||||
- [Error Boundary Middleware](#error-boundary-middleware)
|
||||
- [Validation Middleware](#validation-middleware)
|
||||
- [Composable Middleware](#composable-middleware)
|
||||
- [Middleware Ordering](#middleware-ordering)
|
||||
|
||||
---
|
||||
|
||||
## Authentication Middleware
|
||||
|
||||
### SSOMiddleware Pattern
|
||||
|
||||
**File:** `/form/src/middleware/SSOMiddleware.ts`
|
||||
|
||||
```typescript
|
||||
export class SSOMiddlewareClient {
|
||||
static verifyLoginStatus(req: Request, res: Response, next: NextFunction): void {
|
||||
const token = req.cookies.refresh_token;
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Not authenticated' });
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, config.tokens.jwt);
|
||||
res.locals.claims = decoded;
|
||||
res.locals.effectiveUserId = decoded.sub;
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Audit Middleware with AsyncLocalStorage
|
||||
|
||||
### Excellent Pattern from Blog API
|
||||
|
||||
**File:** `/form/src/middleware/auditMiddleware.ts`
|
||||
|
||||
```typescript
|
||||
import { AsyncLocalStorage } from 'async_hooks';
|
||||
|
||||
export interface AuditContext {
|
||||
userId: string;
|
||||
userName?: string;
|
||||
impersonatedBy?: string;
|
||||
sessionId?: string;
|
||||
timestamp: Date;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export const auditContextStorage = new AsyncLocalStorage<AuditContext>();
|
||||
|
||||
export function auditMiddleware(req: Request, res: Response, next: NextFunction): void {
|
||||
const context: AuditContext = {
|
||||
userId: res.locals.effectiveUserId || 'anonymous',
|
||||
userName: res.locals.claims?.preferred_username,
|
||||
impersonatedBy: res.locals.isImpersonating ? res.locals.originalUserId : undefined,
|
||||
timestamp: new Date(),
|
||||
requestId: req.id || uuidv4(),
|
||||
};
|
||||
|
||||
auditContextStorage.run(context, () => {
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// Getter for current context
|
||||
export function getAuditContext(): AuditContext | null {
|
||||
return auditContextStorage.getStore() || null;
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Context propagates through entire request
|
||||
- No need to pass context through every function
|
||||
- Automatically available in services, repositories
|
||||
- Type-safe context access
|
||||
|
||||
**Usage in Services:**
|
||||
```typescript
|
||||
import { getAuditContext } from '../middleware/auditMiddleware';
|
||||
|
||||
async function someOperation() {
|
||||
const context = getAuditContext();
|
||||
console.log('Operation by:', context?.userId);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Boundary Middleware
|
||||
|
||||
### Comprehensive Error Handler
|
||||
|
||||
**File:** `/form/src/middleware/errorBoundary.ts`
|
||||
|
||||
```typescript
|
||||
export function errorBoundary(
|
||||
error: Error,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
// Determine status code
|
||||
const statusCode = getStatusCodeForError(error);
|
||||
|
||||
// Capture to Sentry
|
||||
Sentry.withScope((scope) => {
|
||||
scope.setLevel(statusCode >= 500 ? 'error' : 'warning');
|
||||
scope.setTag('error_type', error.name);
|
||||
scope.setContext('error_details', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
|
||||
// User-friendly response
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: getUserFriendlyMessage(error),
|
||||
code: error.name,
|
||||
},
|
||||
requestId: Sentry.getCurrentScope().getPropagationContext().traceId,
|
||||
});
|
||||
}
|
||||
|
||||
// Async wrapper
|
||||
export function asyncErrorWrapper(
|
||||
handler: (req: Request, res: Response, next: NextFunction) => Promise<any>
|
||||
) {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await handler(req, res, next);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Composable Middleware
|
||||
|
||||
### withAuthAndAudit Pattern
|
||||
|
||||
```typescript
|
||||
export function withAuthAndAudit(...authMiddleware: any[]) {
|
||||
return [
|
||||
...authMiddleware,
|
||||
auditMiddleware,
|
||||
];
|
||||
}
|
||||
|
||||
// Usage
|
||||
router.post('/:formID/submit',
|
||||
...withAuthAndAudit(SSOMiddlewareClient.verifyLoginStatus),
|
||||
async (req, res) => controller.submit(req, res)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Middleware Ordering
|
||||
|
||||
### Critical Order (Must Follow)
|
||||
|
||||
```typescript
|
||||
// 1. Sentry request handler (FIRST)
|
||||
app.use(Sentry.Handlers.requestHandler());
|
||||
|
||||
// 2. Body parsing
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 3. Cookie parsing
|
||||
app.use(cookieParser());
|
||||
|
||||
// 4. Auth initialization
|
||||
app.use(SSOMiddleware.initialize());
|
||||
|
||||
// 5. Routes registered here
|
||||
app.use('/api/users', userRoutes);
|
||||
|
||||
// 6. Error handler (AFTER routes)
|
||||
app.use(errorBoundary);
|
||||
|
||||
// 7. Sentry error handler (LAST)
|
||||
app.use(Sentry.Handlers.errorHandler());
|
||||
```
|
||||
|
||||
**Rule:** Error handlers MUST be registered AFTER all routes!
|
||||
|
||||
---
|
||||
|
||||
**Related Files:**
|
||||
- [SKILL.md](SKILL.md)
|
||||
- [routing-and-controllers.md](routing-and-controllers.md)
|
||||
- [async-and-errors.md](async-and-errors.md)
|
||||
Reference in New Issue
Block a user