or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

body-processing.mderror-handling.mdfiltering.mdindex.mdnetwork-configuration.mdpath-resolution.mdrequest-modification.mdresponse-processing.md
tile.json

error-handling.mddocs/

Error Handling

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.

Capabilities

Custom Proxy Error Handler

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);
    }
  }
}));

Default Error Behavior

By default, express-http-proxy:

  • Passes all errors except ECONNRESET to Express next() function
  • Returns ECONNRESET errors directly to the user (for historical reasons)
  • Allows your application to handle most errors through standard Express error middleware
// 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' });
});

Advanced Error Handling Patterns

Circuit Breaker Pattern

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;
  }
}));

Retry Logic

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
})));

Fallback Service

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);
  }
}));

Error Types and Handling

Network Errors

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
    });
  }
}));

HTTP Status Code Handling

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();
  }
});

Monitoring and Logging

Comprehensive Error Logging

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()
    });
  }
}));

Health Check Integration

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);
});