Express middleware for proxying HTTP requests to another host and passing the response back to the original caller
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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);
});