Customize error handling behavior for proxy failures, network issues, and application errors. Express-http-proxy provides flexible error handling with customizable error handlers and built-in error recovery patterns.
Override the default error handling behavior by providing a custom error handler function.
/**
* Custom error handler for proxy errors
* @param {Error} err - The error object
* @param {object} res - Express response object
* @param {function} next - Express next function
* @returns {void}
*/
proxyErrorHandler?: (err: Error, res: Response, next: Function) => void;Usage Examples:
const proxy = require('express-http-proxy');
// Basic custom error handling
app.use('/api', proxy('backend.example.com', {
proxyErrorHandler: function(err, res, next) {
console.error('Proxy error:', err.message);
res.status(502).json({
error: 'Proxy Error',
message: 'Unable to connect to backend service',
timestamp: new Date().toISOString()
});
}
}));
// Error code-specific handling
app.use('/resilient', proxy('unreliable-service.com', {
proxyErrorHandler: function(err, res, next) {
switch (err && err.code) {
case 'ECONNRESET':
return res.status(503).json({
error: 'Connection Reset',
message: 'Connection to service was reset',
retry: true
});
case 'ECONNREFUSED':
return res.status(503).json({
error: 'Connection Refused',
message: 'Backend service is unavailable',
retry: false
});
case 'ETIMEDOUT':
return res.status(504).json({
error: 'Timeout',
message: 'Backend service took too long to respond',
retry: true
});
default:
// Pass other errors to Express error handling
next(err);
}
}
}));By default, express-http-proxy:
ECONNRESET to Express next() functionECONNRESET errors directly to the user (for historical reasons)// Default behavior (no custom error handler)
app.use('/default', proxy('api.example.com'));
// Express error middleware will handle proxy errors
app.use((error, req, res, next) => {
console.error('Application error:', error);
res.status(500).json({ error: 'Internal Server Error' });
});class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.threshold = threshold;
this.timeout = timeout;
this.failures = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
canMakeRequest() {
if (this.state === 'CLOSED') return true;
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'HALF_OPEN';
return true;
}
return false;
}
return true; // HALF_OPEN
}
recordSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
recordFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.threshold) {
this.state = 'OPEN';
}
}
}
const circuitBreaker = new CircuitBreaker();
app.use('/circuit-breaker', proxy('flaky-service.com', {
filter: function(req, res) {
if (!circuitBreaker.canMakeRequest()) {
res.status(503).json({
error: 'Circuit Breaker Open',
message: 'Service is temporarily unavailable'
});
return false;
}
return true;
},
proxyErrorHandler: function(err, res, next) {
circuitBreaker.recordFailure();
res.status(503).json({
error: 'Service Unavailable',
message: 'Backend service is experiencing issues',
circuitBreakerState: circuitBreaker.state
});
},
userResHeaderDecorator: function(headers, userReq, userRes, proxyReq, proxyRes) {
if (proxyRes.statusCode < 400) {
circuitBreaker.recordSuccess();
}
return headers;
}
}));async function withRetry(proxyConfig, maxRetries = 3) {
return {
...proxyConfig,
proxyErrorHandler: function(err, res, next) {
const retryCount = (res.locals.retryCount || 0) + 1;
if (retryCount <= maxRetries && shouldRetry(err)) {
console.log(`Retry attempt ${retryCount}/${maxRetries} for error:`, err.message);
// Store retry count and retry the request
res.locals.retryCount = retryCount;
// Delay before retry (exponential backoff)
const delay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000);
setTimeout(() => {
// Retry logic would need to be implemented at a higher level
// This is a simplified example
next(err);
}, delay);
} else {
res.status(502).json({
error: 'Service Unavailable',
message: `Failed after ${retryCount} attempts`,
lastError: err.message
});
}
}
};
}
function shouldRetry(err) {
const retryableCodes = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED'];
return retryableCodes.includes(err.code);
}
app.use('/retry', proxy('unreliable-api.com', await withRetry({
timeout: 5000
})));app.use('/fallback', proxy('primary-service.com', {
proxyErrorHandler: function(err, res, next) {
console.log('Primary service failed, trying fallback');
// Try fallback service
const fallbackProxy = proxy('fallback-service.com', {
proxyErrorHandler: function(fallbackErr, fallbackRes, fallbackNext) {
// Both services failed
res.status(503).json({
error: 'All Services Unavailable',
primary: err.message,
fallback: fallbackErr.message
});
}
});
// Execute fallback proxy
fallbackProxy(res.locals.originalReq, res, next);
}
}));app.use('/network-aware', proxy('api.example.com', {
proxyErrorHandler: function(err, res, next) {
const errorResponses = {
'ECONNRESET': {
status: 502,
error: 'Connection Reset',
message: 'Connection was reset by the server',
action: 'retry'
},
'ECONNREFUSED': {
status: 503,
error: 'Connection Refused',
message: 'Unable to connect to the service',
action: 'check_service'
},
'ETIMEDOUT': {
status: 504,
error: 'Timeout',
message: 'Request timed out',
action: 'retry_with_longer_timeout'
},
'ENOTFOUND': {
status: 502,
error: 'DNS Resolution Failed',
message: 'Unable to resolve hostname',
action: 'check_configuration'
},
'ECONNABORTED': {
status: 499,
error: 'Request Aborted',
message: 'Request was aborted by client',
action: 'none'
}
};
const errorInfo = errorResponses[err.code] || {
status: 500,
error: 'Unknown Error',
message: err.message,
action: 'contact_support'
};
res.status(errorInfo.status).json({
...errorInfo,
timestamp: new Date().toISOString(),
requestId: res.locals.requestId
});
}
}));app.use('/status-aware', proxy('api.example.com', {
skipToNextHandlerFilter: function(proxyRes) {
// Handle specific status codes differently
if (proxyRes.statusCode === 429) {
// Rate limited - let Express handle it
return true;
}
return false;
},
proxyErrorHandler: function(err, res, next) {
// This handles connection/network errors
res.status(502).json({
error: 'Network Error',
message: err.message,
type: 'connection'
});
}
}));
// Express middleware to handle rate limiting
app.use((req, res, next) => {
if (res.statusCode === 429) {
res.status(503).json({
error: 'Rate Limited',
message: 'Too many requests, please try again later',
type: 'rate_limit'
});
} else {
next();
}
});app.use('/monitored', proxy('api.example.com', {
proxyErrorHandler: function(err, res, next) {
// Log error details
const errorLog = {
timestamp: new Date().toISOString(),
error: {
message: err.message,
code: err.code,
stack: err.stack
},
request: {
method: res.locals.originalReq?.method,
url: res.locals.originalReq?.url,
headers: res.locals.originalReq?.headers,
userAgent: res.locals.originalReq?.headers?.['user-agent']
},
response: {
statusCode: res.statusCode
}
};
console.error('Proxy Error:', JSON.stringify(errorLog, null, 2));
// Send to monitoring service
if (process.env.MONITORING_ENDPOINT) {
fetch(process.env.MONITORING_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorLog)
}).catch(monitoringErr => {
console.error('Failed to send error to monitoring:', monitoringErr);
});
}
// Return user-friendly error
res.status(502).json({
error: 'Service Temporarily Unavailable',
requestId: generateRequestId()
});
}
}));let serviceHealth = {
status: 'healthy',
lastError: null,
errorCount: 0,
lastSuccessTime: Date.now()
};
app.use('/health-monitored', proxy('api.example.com', {
proxyErrorHandler: function(err, res, next) {
// Update health status
serviceHealth.status = 'unhealthy';
serviceHealth.lastError = err.message;
serviceHealth.errorCount++;
res.status(503).json({
error: 'Service Unhealthy',
health: serviceHealth
});
},
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
// Update health on successful responses
if (proxyRes.statusCode < 400) {
serviceHealth.status = 'healthy';
serviceHealth.lastSuccessTime = Date.now();
serviceHealth.errorCount = Math.max(0, serviceHealth.errorCount - 1);
}
return proxyResData;
}
}));
// Health check endpoint
app.get('/health', (req, res) => {
res.json(serviceHealth);
});