Opinionated backend development standards for Node.js + Express + TypeScript microservices. Covers layered architecture, BaseController pattern, dependency injection, Prisma repositories, Zod validation, unifiedConfig, Sentry error tracking, async safety, and testing discipline.
71
71%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
Complete guide to creating and using middleware in backend microservices.
File: /form/src/middleware/SSOMiddleware.ts
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' });
}
}
}File: /form/src/middleware/auditMiddleware.ts
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:
Usage in Services:
import { getAuditContext } from '../middleware/auditMiddleware';
async function someOperation() {
const context = getAuditContext();
console.log('Operation by:', context?.userId);
}File: /form/src/middleware/errorBoundary.ts
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);
}
};
}export function withAuthAndAudit(...authMiddleware: any[]) {
return [
...authMiddleware,
auditMiddleware,
];
}
// Usage
router.post('/:formID/submit',
...withAuthAndAudit(SSOMiddlewareClient.verifyLoginStatus),
async (req, res) => controller.submit(req, res)
);// 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: