# 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(); 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 ) { 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)