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, andX-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:
connectionkeep-aliveproxy-authenticateproxy-authorizationtetrailertransfer-encodingupgradehostcontent-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
Authorizationheader and replace it with custom internal identity headers such asX-User-IdorX-User-Roles. - Timeout Handling: Always configure explicit
timeouts in your Axios configuration (
timeout: 5000) to prevent downstream latency from exhausting gateway sockets.