Express HTTP Proxy Middleware
express-http-proxy is an Express.js middleware designed for proxying incoming HTTP requests to a specified target host. It offers extensive customization through hooks, enabling developers to modify incoming requests before they reach the proxy target and decorate outgoing responses before they are returned to the client. The package is currently at version 2.1.2 and is actively maintained, having received updates like the significant v2.0.0 release that upgraded underlying dependencies and ensured compatibility with modern Node.js environments (16, 18, and 20). While not on a strict, frequent release cadence, updates are issued to address compatibility and dependency needs, especially for critical Node.js versions. Its key differentiator lies in its comprehensive set of configurable hooks for request and response transformation, making it suitable for scenarios requiring fine-grained control over proxy behavior within an Express application, such as API gateways or hiding internal service endpoints.
Common errors
-
Error: Can't set headers after they are sent to the client.
cause This error typically occurs when a response is attempted to be modified or sent more than once within the Express middleware chain, often after the proxy has already sent its response.fixEnsure that your custom `decorateUserRes` or `proxyErrorHandler` functions do not attempt to send a response or modify headers on the `userRes` object if `express-http-proxy` has already done so. Use `res.headersSent` to check if headers have already been sent before attempting modification. -
connect ECONNREFUSED 127.0.0.1:80 (or similar network error like getaddrinfo EAI_AGAIN)
cause The `express-http-proxy` middleware attempted to connect to the target host but was refused, or the DNS resolution failed. This usually means the target server is down, unreachable, or the hostname is incorrect.fixVerify that the target host specified in `proxy(host, options)` is correct and that the target server is running and accessible from the machine running your Express application. Check firewalls or network configurations if the target is external. -
Property 'proxy' does not exist on type 'typeof import("/.../node_modules/express-http-proxy/index")'.cause When using TypeScript, this error indicates that the type definitions for `express-http-proxy` are not correctly installed or configured, so TypeScript cannot infer the `proxy` function's signature.fixInstall the type definitions: `npm install --save-dev @types/express-http-proxy`. Ensure your `tsconfig.json` includes `node_modules/@types` in its `typeRoots` or that the types are correctly picked up by your IDE. -
504 Gateway Timeout or an 'x-timeout-reason: express-http-proxy reset the request' header in the response.
cause The proxied request took too long to receive a response from the upstream target, exceeding either Node.js's default HTTP timeout or a custom timeout configured in `express-http-proxy`.fixIncrease the `timeout` option within the `express-http-proxy` configuration, e.g., `proxy(host, { timeout: 120000 })` for 120 seconds. If the issue persists, examine the performance of the upstream service or network latency.
Warnings
- breaking Version 2.0.0 and above explicitly target Node.js 16, 18, and 20 for compatibility. Running on older Node.js versions (e.g., Node.js 14 or earlier) may lead to unexpected behavior or runtime errors due to updated underlying dependencies.
- gotcha As of approximately v1.2.0 (referenced as v.1.20 in release notes), `express-http-proxy` defaults to streaming proxy responses. This can alter behavior if your application previously relied on buffered responses or expected specific non-streaming handling, potentially affecting custom response decorators.
- gotcha Node.js's default HTTP request timeout is 2 minutes. Long-running proxied requests exceeding this duration may result in `504 Gateway Timeout` errors or `ECONNRESET` messages. `express-http-proxy` itself may reset the connection with an `x-timeout-reason` header.
- gotcha When proxying requests with large bodies (e.g., file uploads), you may encounter an `Error: request entity too large`. This often occurs because Express or underlying body parsers have default size limits that are exceeded by the proxied request.
- gotcha Improper configuration of `express-http-proxy`, particularly allowing user-controlled input to determine the proxy target host without strict validation, can lead to an open proxy or Server-Side Request Forgery (SSRF) vulnerabilities.
Install
-
npm install express-http-proxy -
yarn add express-http-proxy -
pnpm add express-http-proxy
Imports
- proxy
import proxy from 'express-http-proxy';
const proxy = require('express-http-proxy'); - proxy
import { proxy } from 'express-http-proxy';import proxy from 'express-http-proxy';
- ProxyOptions
import { ProxyOptions } from 'express-http-proxy';import type { ProxyOptions } from 'express-http-proxy';
Quickstart
import express from 'express';
import proxy from 'express-http-proxy';
import type { Request, Response, NextFunction } from 'express';
const app = express();
const PORT = process.env.PORT || 3000;
const PROXY_TARGET = process.env.PROXY_TARGET || 'https://httpbin.org'; // A public test service
// Middleware to log all incoming requests before proxying
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`Incoming request: ${req.method} ${req.originalUrl}`);
next();
});
// Configure the proxy middleware
app.use(
'/proxy',
proxy(PROXY_TARGET, {
// Resolve the path for the proxied request
proxyReqPathResolver: (req: Request) => {
const resolvedPath = req.url.startsWith('/proxy') ? req.url.slice('/proxy'.length) : req.url;
console.log(`Resolved proxy path to: ${resolvedPath}`);
return resolvedPath;
},
// Decorate the request going to the proxy target
decorateProxyReq: (proxyReq: any, req: Request) => {
console.log(`Adding custom header to proxied request for ${req.path}`);
proxyReq.setHeader('X-Proxy-By', 'express-http-proxy-example');
return proxyReq;
},
// Decorate the response coming back from the proxy target
decorateUserRes: (proxyRes: any, proxyResData: Buffer, userReq: Request, userRes: Response) => {
console.log(`Received proxied response status: ${proxyRes.statusCode}`);
// Example: modify response data (e.g., inject a footer)
const data = proxyResData.toString('utf8');
if (proxyRes.headers['content-type']?.includes('text/html')) {
return data.replace('</body>', '<p><i>Proxied via express-http-proxy!</i></p></body>');
}
return proxyResData; // Return original data if not HTML
},
// Custom error handler for proxy errors
proxyErrorHandler: (err: any, res: Response, next: NextFunction) => {
console.error('Proxy error:', err);
res.status(500).send('Proxy Error: Could not reach target.');
}
})
);
// Default route for non-proxied requests
app.get('/', (req: Request, res: Response) => {
res.send('Hello from the main Express app! Try /proxy/get to use the proxy.');
});
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
console.log(`Proxying requests from /proxy to ${PROXY_TARGET}`);
console.log(`Try visiting http://localhost:${PORT}/proxy/get for a proxied request.`);
});