Forward Incoming Headers with Axios in Node.js

This guide explains how to properly capture incoming client HTTP request headers in a Node.js API gateway and forward them to downstream services using Axios. You will learn how to identify necessary context headers, sanitize hop-by-hop headers that can break upstream requests, and implement a reusable Express middleware pattern for seamless request proxying.

Why Header Forwarding Matters

In a microservices architecture, an API gateway acts as the single entry point for client requests. Forwarding headers is necessary to preserve crucial context, such as:

  • Authentication & Authorization: Passing Authorization (Bearer tokens, API keys) downstream.
  • Distributed Tracing: Preserving correlation IDs like X-Request-ID, X-Correlation-ID, or OpenTelemetry headers (traceparent).
  • Client Context: Passing client metadata like User-Agent, Accept-Language, and X-Forwarded-For.

Handling Hop-by-Hop Headers

You should never forward all incoming headers blindly. HTTP distinguishes between end-to-end headers (intended for the final recipient) and hop-by-hop headers (meaningful only for a single transport-level connection). Forwarding hop-by-hop headers or the host header can cause downstream network timeouts, connection resets, or payload mismatches.

Standard hop-by-hop headers to remove include:

  • connection
  • keep-alive
  • proxy-authenticate
  • proxy-authorization
  • te
  • trailer
  • transfer-encoding
  • upgrade
  • host
  • content-length (Axios will recalculate this based on the outgoing payload)

Implementation in Express and Axios

Below is a complete implementation demonstrating how to filter incoming headers and pass them through Axios.

const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

// List of hop-by-hop headers to exclude
const HOP_BY_HOP_HEADERS = new Set([
  'connection',
  'keep-alive',
  'proxy-authenticate',
  'proxy-authorization',
  'te',
  'trailer',
  'transfer-encoding',
  'upgrade',
  'host',
  'content-length'
]);

/**
 * Filter out hop-by-hop headers and attach proxy tracking headers
 */
function prepareForwardHeaders(incomingHeaders, clientIp) {
  const forwardedHeaders = {};

  for (const [key, value] of Object.entries(incomingHeaders)) {
    if (!HOP_BY_HOP_HEADERS.has(key.toLowerCase())) {
      forwardedHeaders[key] = value;
    }
  }

  // Append or set standard proxy headers
  forwardedHeaders['x-forwarded-for'] = incomingHeaders['x-forwarded-for']
    ? `${incomingHeaders['x-forwarded-for']}, ${clientIp}`
    : clientIp;

  return forwardedHeaders;
}

// Gateway route forwarding requests to a microservice
app.all('/service/*', async (req, res) => {
  const downstreamBaseUrl = 'https://api.internal-service.local';
  const targetUrl = `${downstreamBaseUrl}${req.originalUrl.replace('/service', '')}`;

  const headers = prepareForwardHeaders(req.headers, req.ip);

  try {
    const response = await axios({
      method: req.method,
      url: targetUrl,
      headers: headers,
      data: req.body,
      params: req.query,
      validateStatus: () => true // Forward all HTTP status codes directly
    });

    // Send the downstream response back to the client
    res.status(response.status).set(response.headers).send(response.data);
  } catch (error) {
    res.status(502).json({
      error: 'Bad Gateway',
      message: error.message
    });
  }
});

app.listen(3000, () => {
  console.log('API Gateway running on port 3000');
});

Key Considerations

  • Header Mutations: Some headers are transformed into lowercase by Node.js HTTP parsers (req.headers). Axios handles lowercase keys correctly without issues.
  • Sensitive Headers: If your gateway terminates authentication (e.g., converting a public JWT into an internal user object), strip the incoming Authorization header and replace it with custom internal identity headers such as X-User-Id or X-User-Roles.
  • Timeout Handling: Always configure explicit timeouts in your Axios configuration (timeout: 5000) to prevent downstream latency from exhausting gateway sockets.