/**
* Error Handler Middleware - Catches and formats all Express errors
*
* Primary middleware for catching errors from routes and returning
* structured JSON responses. Integrates with logError utility for
* comprehensive error logging with request context.
*/
import type { NextFunction, Request, Response } from "express";
import { logError } from "../utils/errors.js";
import { formatErrorResponse, mapErrorToStatus } from "./error-mapper.js";
import "./types.js"; // Import Request type augmentation
/**
* Primary error handler middleware
*
* Catches all errors from routes and:
* - Logs error with full context (request ID, IP, user agent, etc.)
* - Maps error to appropriate HTTP status code
* - Formats error response with consistent structure
* - Includes stack traces in non-production environments
* - Handles headers-already-sent scenario gracefully
*
* @param error - Error thrown from route or middleware
* @param req - Express request object (with requestId from Phase 1)
* @param res - Express response object
* @param next - Express next function (for headers-already-sent case)
*
* @example
* app.use(errorHandler);
* app.use(fallbackErrorHandler); // Safety net
*/
export function errorHandler(
error: unknown,
req: Request,
res: Response,
next: NextFunction
): void {
// Check if response already sent (can't send error response)
if (res.headersSent) {
next(error);
return;
}
// Get request ID from Phase 1 middleware (or fallback to 'unknown')
const requestId = req.requestId || "unknown";
// Log error with comprehensive context
logError(error, {
requestId,
operation: `${req.method} ${req.path}`,
metadata: {
ip: req.ip,
userAgent: req.headers["user-agent"],
forwarded: req.headers["x-forwarded-for"] || req.headers["x-real-ip"],
},
});
// Determine HTTP status code based on error type
const statusCode = mapErrorToStatus(error);
// Format error response (include stack trace in non-production)
const includeStack = process.env.NODE_ENV !== "production";
const errorResponse = formatErrorResponse(error, requestId, includeStack);
// Send JSON response
res.status(statusCode).json(errorResponse);
}
/**
* Fallback error handler (last-resort safety net)
*
* Handles errors that occur during primary error handling.
* Prevents server crash if primary handler fails.
*
* This should be registered AFTER errorHandler in Express:
* app.use(errorHandler);
* app.use(fallbackErrorHandler);
*
* @param error - Error thrown during error handling
* @param req - Express request object
* @param res - Express response object
* @param _next - Express next function (unused)
*
* @example
* app.use(fallbackErrorHandler);
*/
export function fallbackErrorHandler(
error: unknown,
req: Request,
res: Response,
_next: NextFunction
): void {
// Log critical error with structured context
logError(error, { operation: "fallbackErrorHandler", requestId: req.requestId });
// Check if response already sent
if (res.headersSent) {
return;
}
// Send minimal error response
res.status(500).json({
error: {
message: "An unexpected error occurred",
code: "INTERNAL_ERROR",
requestId: req.requestId || "unknown",
},
});
}