CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-express-http-proxy

Express middleware for proxying HTTP requests to another host and passing the response back to the original caller

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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

docs

body-processing.md

error-handling.md

filtering.md

index.md

network-configuration.md

path-resolution.md

request-modification.md

response-processing.md

tile.json