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:

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:

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