@reedchan/koa-http-proxy v1.0.1
koa-http-proxy
A powerful Koa middleware for proxying HTTP requests to another host with advanced features like retry mechanisms, streaming support, and request/response transformation.
This repository is forked from koa-better-http-proxy with significant enhancements.
π Languages: English | δΈζ
π Table of Contents
- β¨ Features
- π¦ Installation
- π Quick Start
- π Usage Guide
- βοΈ Configuration Reference
- π Related Resources
β¨ Features
| Feature | Description | Status |
|---|---|---|
| π Smart Retry | Automatic retry with exponential backoff and custom logic | β |
| π Streaming Support | True streaming for large files and real-time data | β |
| π§ Request/Response Transformation | Modify headers, body, and paths with full async support | β |
| π Memory Management | Automatic protection against OOM with large files | β |
| β±οΈ Timeout Control | Configurable connection and request timeouts | β |
| π‘οΈ Circuit Breaker | Built-in patterns for fault tolerance | β |
| π TypeScript Support | Full type definitions included | β |
| π Conditional Proxy | Filter requests with custom logic | β |
| ποΈ Compression | Automatic gzip/deflate handling | β |
| π Session Preservation | Maintain user sessions across proxies | β |
π¦ Installation
npm install @reedchan/koa-http-proxy --saveπ Quick Start
Basic Proxy
const Koa = require('koa');
const proxy = require('@reedchan/koa-http-proxy');
const app = new Koa();
// Simple proxy to another host
app.use(proxy('api.example.com'));
app.listen(3000);API Gateway Pattern
const app = new Koa();
// Route different paths to different services
app.use('/api/users', proxy('user-service.internal'));
app.use('/api/orders', proxy('order-service.internal'));
app.use('/api/auth', proxy('auth-service.internal'));
app.listen(3000);Load Balancer Pattern
const servers = ['server1.com', 'server2.com', 'server3.com'];
let currentServer = 0;
app.use(proxy(() => {
const server = servers[currentServer];
currentServer = (currentServer + 1) % servers.length;
return server;
}));π Usage Guide
Basic Proxy
The simplest use case - proxy all requests to another host:
app.use(async (ctx) => {
// Stop after proxy (don't continue to next middleware)
await proxy('api.backend.com', {
port: 443,
https: true
})(ctx);
});Koa Native Middleware Pattern β
Following Koa's native design philosophy, you have complete control over middleware execution:
// β
Stop after proxy - don't pass next
app.use(async (ctx) => {
await proxy('api.backend.com')(ctx);
// No next() called, execution stops here
});
// β
Continue after proxy - pass next
app.use(async (ctx, next) => {
await proxy('api.backend.com')(ctx, next);
// next() will be called, continues to next middleware
});
// β
Dynamic control based on conditions
app.use(async (ctx, next) => {
const shouldContinue = ctx.path.startsWith('/api/');
if (shouldContinue) {
await proxy('api.backend.com')(ctx, next); // Continue
} else {
await proxy('api.backend.com')(ctx); // Stop
}
});Solving Route Duplication Issues
The Problem:
// β Both routes execute for the same request
router.post('/upload/:version/files', proxy('api.com')); // Executes
router.all('/(.*)', proxy('api.com')); // Also executes!The Solution (Koa Native Way):
// β
Clean, explicit control
router.post('/upload/:version/files', async (ctx) => {
await proxy('api.com')(ctx); // Don't pass next - stops here
});
router.all('/(.*)', async (ctx, next) => {
await proxy('api.com')(ctx, next); // Continue if needed
});Streaming Mode
Perfect for file uploads, downloads, and real-time data:
// Enable streaming for file uploads
app.use('/upload', async (ctx) => {
await proxy('fileserver.com', {
parseReqBody: false, // Enable streaming
limit: '500mb', // Support large files
timeout: 300000 // 5 minute timeout
})(ctx); // Stop after upload
});
// Smart conditional streaming
app.use(async (ctx, next) => {
const proxy_fn = proxy('backend.com', {
parseReqBody: (ctx) => {
const size = parseInt(ctx.headers['content-length'] || '0');
return size < 20 * 1024 * 1024; // Stream files >20MB
}
});
if (ctx.path.includes('/upload')) {
await proxy_fn(ctx); // Stop after upload
} else {
await proxy_fn(ctx, next); // Continue for other requests
}
});Retry Configuration
Robust retry mechanisms for unreliable networks:
// Simple retry with defaults
app.use(proxy('api.backend.com', {
retry: true // 3 retries with exponential backoff
}));
// Custom retry configuration
app.use(proxy('api.backend.com', {
retry: {
retries: 5,
maxRetryTime: 30000,
minTimeout: 500,
maxTimeout: 5000
}
}));
// Advanced custom retry logic
app.use(proxy('api.backend.com', {
retry: async (handle, ctx) => {
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const result = await handle();
if (result.proxy.res.statusCode < 500) return result;
if (attempt < 3) {
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
} catch (error) {
if (attempt === 3) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}));Request/Response Transformation
Modify requests and responses on the fly:
app.use(proxy('api.backend.com', {
// Transform request path
proxyReqPathResolver: (ctx) => {
return ctx.path.replace('/api/v1', '/api/v2');
},
// Add authentication headers
proxyReqOptDecorator: (proxyReqOpts, ctx) => {
proxyReqOpts.headers['Authorization'] = ctx.headers['authorization'];
return proxyReqOpts;
},
// Transform response
userResDecorator: (proxyRes, proxyResData, ctx) => {
const data = JSON.parse(proxyResData.toString());
data.timestamp = new Date().toISOString();
return JSON.stringify(data);
}
}));βοΈ Configuration Reference
Core Options
agent
Use a custom http.Agent for proxy requests.
const agent = new http.Agent({ keepAlive: true });
app.use(proxy('api.backend.com', { agent }));port
The port to use for the proxied host.
app.use(proxy('api.backend.com', { port: 8080 }));https
Force HTTPS for the proxy request.
app.use(proxy('api.backend.com', { https: true }));headers
Additional headers to send to the proxied host.
app.use(proxy('api.backend.com', {
headers: {
'X-API-Key': 'your-api-key',
'User-Agent': 'MyApp/1.0'
}
}));strippedHeaders
Headers to remove from proxy response.
app.use(proxy('api.backend.com', {
strippedHeaders: ['set-cookie', 'x-internal-header']
}));Request Processing
filter
Filter which requests should be proxied.
app.use(proxy('api.backend.com', {
filter: (ctx) => {
return ctx.method === 'GET' && ctx.path.startsWith('/api');
}
}));proxyReqPathResolver
Transform the request path before proxying.
app.use(proxy('api.backend.com', {
proxyReqPathResolver: (ctx) => {
return ctx.path.replace(/^\/api/, '');
}
}));proxyReqOptDecorator
Modify request options before sending.
app.use(proxy('api.backend.com', {
proxyReqOptDecorator: (proxyReqOpts, ctx) => {
proxyReqOpts.headers['X-Forwarded-For'] = ctx.ip;
return proxyReqOpts;
}
}));proxyReqBodyDecorator
Transform request body before sending.
app.use(proxy('api.backend.com', {
proxyReqBodyDecorator: (bodyContent, ctx) => {
const data = JSON.parse(bodyContent);
data.clientInfo = { ip: ctx.ip, userAgent: ctx.get('User-Agent') };
return JSON.stringify(data);
}
}));Response Processing
userResDecorator
Transform response data before sending to client.
app.use(proxy('api.backend.com', {
userResDecorator: (proxyRes, proxyResData, ctx) => {
const data = JSON.parse(proxyResData.toString());
data.processedAt = new Date().toISOString();
return JSON.stringify(data);
}
}));userResHeadersDecorator
Transform response headers.
app.use(proxy('api.backend.com', {
userResHeadersDecorator: (headers) => {
headers['X-Proxy-By'] = 'koa-http-proxy';
delete headers['x-internal-header'];
return headers;
}
}));Body Processing
parseReqBody
Control request body parsing (boolean or function).
// Disable for streaming
app.use(proxy('api.backend.com', { parseReqBody: false }));
// Conditional parsing
app.use(proxy('api.backend.com', {
parseReqBody: (ctx) => {
return !ctx.path.includes('/upload');
}
}));reqAsBuffer
Ensure request body is encoded as Buffer.
app.use(proxy('api.backend.com', { reqAsBuffer: true }));reqBodyEncoding
Encoding for request body (default: 'utf-8').
app.use(proxy('api.backend.com', { reqBodyEncoding: 'binary' }));limit
Body size limit (default: '1mb').
app.use(proxy('api.backend.com', { limit: '50mb' }));Session & Security
preserveReqSession
Pass session along to proxied request.
app.use(proxy('api.backend.com', { preserveReqSession: true }));preserveHostHdr
Copy the host HTTP header to proxied request.
app.use(proxy('api.backend.com', { preserveHostHdr: true }));Timeout Configuration
connectTimeout
Timeout for initial connection.
app.use(proxy('api.backend.com', { connectTimeout: 5000 }));timeout
Overall request timeout.
app.use(proxy('api.backend.com', { timeout: 30000 }));Debug Options
debug
Enable detailed request logging for debugging and monitoring.
// Enable basic debug logging
app.use(proxy('api.backend.com', { debug: true }));
// Use object configuration for debug options
app.use(proxy('api.backend.com', {
debug: {
enabled: true,
includeBody: true // Include request body content
}
}));Example Output:
======================================= KOA-HTTP-PROXY DEBUG =======================================
POST https://api.backend.com/users
Payload Size: 256 B
Headers:
{
"content-type": "application/json",
"authorization": "Bearer token123",
"user-agent": "MyApp/1.0",
"content-length": 256
}
Request Body:
{
"name": "John Doe",
"email": "john@example.com"
}
====================================================================================================Configuration Options:
enabled: Whether to enable debug logging (default: false)includeBody: Whether to include request body content in logs (default: false)
Features:
- Automatically hide standard ports (HTTP 80, HTTPS 443)
- Smart JSON formatting
- No body parsing for GET/HEAD/DELETE/OPTIONS requests
- Precise file size display
Use Cases:
- Development debugging
- API monitoring
- Performance analysis
- Request troubleshooting
Retry Configuration
Simple Retry
app.use(proxy('api.backend.com', { retry: true }));Advanced Retry
app.use(proxy('api.backend.com', {
retry: {
retries: 5, // Max retry attempts
maxRetryTime: 30000, // Total retry time limit
minTimeout: 1000, // Initial delay
maxTimeout: 10000 // Maximum delay
}
}));Custom Retry Function
app.use(proxy('api.backend.com', {
retry: async (handle, ctx) => {
// Custom retry logic
let result;
for (let i = 0; i < 3; i++) {
result = await handle();
if (result.proxy.res.statusCode < 500) break;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
return result;
}
}));β οΈ Memory Warning: Retry caches request body in memory. For large files (>20MB), retry is automatically disabled. Use
parseReqBody: falsefor streaming mode.
π Related Resources
- Streaming Guide - Comprehensive guide for handling large files and real-time streams
- Examples - Working examples for different use cases
- TypeScript Definitions - Full type definitions for TypeScript users
Made with β€οΈ by the community | Report Issues | Contribute