TypeScript-ready Express middleware for proxying API requests with zero configuration needed. Perfect for API gateways and microservices.
http-proxy-middleware.
npm install express-simple-proxyimport express from 'express';
import { createProxyController } from 'express-simple-proxy';
const app = express();
const proxy = createProxyController({
baseURL: 'https://api.example.com'
});
// Direct path mapping - no configuration needed
app.get('/users', proxy());
app.post('/users', proxy());
app.get('/users/:id', proxy());const proxy = createProxyController({
baseURL: 'https://api.example.com',
headers: (req) => ({
'Authorization': `Bearer ${req.headers.authorization}`,
'User-Agent': 'MyApp/1.0'
})
});
app.use('/api', proxy()); // Proxy all /api/* routesconst proxy = createProxyController({
baseURL: 'https://api.example.com',
headers: (req) => ({ 'Authorization': req.headers.authorization }),
errorHandler: (error, req, res) => {
res.status(error.status || 500).json({
success: false,
error: error.message,
timestamp: new Date().toISOString()
});
}
});The key differentiator is omitted proxy paths - when you call proxy() without a path parameter, it uses the original request path:
// Traditional proxy libraries require explicit path mapping:
app.get('/users', proxy('/api/users')); // Maps /users β /api/users
app.get('/users/:id', proxy('/api/users/:id')); // Maps /users/123 β /api/users/123
// Express Simple Proxy - zero configuration:
app.get('/users', proxy()); // Maps /users β /users
app.get('/users/:id', proxy()); // Maps /users/123 β /users/123Benefits:
- β Zero Configuration: No path mapping needed
- β Consistent Routing: Frontend and backend paths stay in sync
- β Automatic Parameter Handling: All path parameters are preserved
- β Perfect for Microservices: Direct service-to-service communication
Built from the ground up with TypeScript, not retrofitted:
import { ProxyConfig, ProxyError, RequestWithLocals } from 'express-simple-proxy';
const config: ProxyConfig = {
baseURL: 'https://api.example.com',
headers: (req: RequestWithLocals) => ({
'Authorization': `Bearer ${req.locals?.token}`
}),
errorHandler: (error: ProxyError, req: RequestWithLocals, res: Response) => {
// Full type safety throughout
}
};Optimized specifically for REST API communication:
| Feature | Express Simple Proxy | General HTTP Proxies |
|---|---|---|
| JSON APIs | β Optimized handling | |
| File Uploads | β Built-in multipart/form-data | β Manual setup |
| Error Processing | β Structured error hooks | |
| TypeScript | β Native & complete | |
| Setup Complexity | π’ Minimal | π‘ Configuration heavy |
| Option | Type | Required | Description |
|---|---|---|---|
baseURL |
string |
β | Base URL for the target API |
headers |
function |
β | Function that returns headers object based on request |
timeout |
number |
β | Request timeout in milliseconds (default: 30000) |
responseHeaders |
function |
β | Function to transform response headers |
errorHandler |
function |
β | Custom error handling function |
errorHandlerHook |
function |
β | Error processing hook function |
const config: ProxyConfig = {
baseURL: 'https://api.example.com',
timeout: 30000,
headers: (req) => ({
'Authorization': `Bearer ${req.locals.token}`,
'Content-Type': 'application/json',
'X-Request-ID': req.headers['x-request-id']
}),
responseHeaders: (response) => ({
'X-Proxy-Response': 'true',
'X-Response-Time': Date.now().toString()
}),
// Error processing hook - runs before error handler
errorHandlerHook: async (error, req, res) => {
// Log to monitoring service
await logErrorToService(error, req);
// Add context to error
error.context = `${req.method} ${req.path}`;
return error;
},
// Custom error response
errorHandler: (error, req, res) => {
const response = {
success: false,
error: {
message: error.message,
code: error.code,
status: error.status
},
meta: {
timestamp: new Date().toISOString(),
requestId: req.headers['x-request-id'],
path: req.path
}
};
res.status(error.status || 500).json(response);
}
};import multer from 'multer';
const upload = multer({ storage: multer.memoryStorage() });
// Single file upload
app.post('/upload', upload.single('file'), proxy());
// Multiple file upload
app.post('/upload-multiple', upload.array('files'), proxy());
// Form data with file
app.post('/profile', upload.single('avatar'), proxy());// Transform response data
app.get('/users', proxy(undefined, (req, res, remoteResponse) => {
res.json({
success: true,
data: remoteResponse.data,
timestamp: new Date().toISOString()
});
}));
// Return raw response
app.get('/raw-data', proxy(undefined, true));// Explicit path mapping for different frontend/backend structures
app.get('/dashboard/users', proxy('/api/admin/users'));
app.get('/public/health', proxy('/internal/health-check'));
// API version mapping
app.get('/v1/users', proxy('/api/v1/users'));
app.get('/latest/users', proxy('/api/v3/users'));const userService = createProxyController({
baseURL: 'https://user-service.internal',
headers: (req) => ({ 'Authorization': req.headers.authorization })
});
const orderService = createProxyController({
baseURL: 'https://order-service.internal',
headers: (req) => ({ 'Authorization': req.headers.authorization })
});
// Clean service routing with omitted paths
app.get('/api/users', userService());
app.post('/api/users', userService());
app.get('/api/users/:id', userService());
app.get('/api/orders', orderService());
app.post('/api/orders', orderService());
app.get('/api/orders/:id', orderService());const tenantProxy = createProxyController({
baseURL: 'https://tenant-api.saas.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Tenant-ID': req.params.tenantId
})
});
// All tenant routes use direct mapping
app.get('/api/tenants/:tenantId/users', tenantProxy());
app.get('/api/tenants/:tenantId/billing', tenantProxy());
app.get('/api/tenants/:tenantId/analytics', tenantProxy());const devProxy = createProxyController({
baseURL: process.env.API_BASE_URL || 'https://api-dev.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Environment': 'development'
})
});
// Mirror production API structure exactly
app.use('/api', devProxy()); // Catch-all for all API routesconst createServiceProxy = (serviceName: string) => {
return createProxyController({
baseURL: `https://${serviceName}.mesh.internal`,
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Correlation-ID': req.headers['x-correlation-id'] || generateId(),
'X-Service-Name': serviceName
})
});
};
const userService = createServiceProxy('user-service');
const notificationService = createServiceProxy('notification-service');
// Service mesh routing with consistent paths
app.get('/api/users', userService());
app.get('/api/notifications', notificationService());A comprehensive collection of practical examples for common use cases. See the Complete Cookbook for detailed recipes.
Authentication & Security:
// JWT Token Forwarding
const proxy = createProxyController({
baseURL: 'https://api.example.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-User-ID': req.user?.id,
'X-Request-ID': crypto.randomUUID()
})
});File Uploads:
import multer from 'multer';
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }
});
app.post('/upload', upload.single('file'), proxy('/api/upload'));Load Balancing:
const servers = ['https://api1.com', 'https://api2.com', 'https://api3.com'];
let current = 0;
app.use('/api', (req, res, next) => {
const selectedServer = servers[current++ % servers.length];
const proxy = createProxyController({
baseURL: selectedServer,
headers: (req) => ({ 'Authorization': req.headers.authorization })
});
proxy()(req, res, next);
});Performance Monitoring:
const proxy = createProxyController({
baseURL: 'https://api.example.com',
errorHandlerHook: async (error, req, res) => {
console.log({
method: req.method,
path: req.path,
duration: Date.now() - req.startTime,
status: error.status
});
return error;
}
});π View Complete Cookbook - Contains 50+ recipes for:
- Authentication & Security (JWT, API Keys, OAuth2)
- File Handling (Uploads, Validation, Chunking)
- Database & Caching (Sharding, Redis, Cache Control)
- Monitoring & Observability (Tracing, Metrics, Health Checks)
- Load Balancing & Failover (Round Robin, Circuit Breakers)
- Development & Testing (Mocking, A/B Testing, Feature Flags)
- Rate Limiting & Throttling (Basic, Per-user)
- Data Transformation (Schema Validation, GraphQL)
- Content Negotiation (Accept Headers, Compression)
- Response Errors (4xx/5xx): Server responded with error status
- Network Errors (503): No response received (timeout, connection refused)
- Request Setup Errors (500): Invalid configuration or malformed data
- Error Occurs β 2. Error Hook Processing β 3. Error Handling β 4. Fallback
const proxy = createProxyController({
baseURL: 'https://api.example.com',
errorHandlerHook: async (error, req, res) => {
// Monitor and alert
await monitoring.logError(error, { method: req.method, path: req.path });
if (error.status >= 500) {
await alerting.sendAlert({
title: 'API Proxy Error',
severity: 'high'
});
}
return error;
},
errorHandler: (error, req, res) => {
// Forward rate limiting headers
if (error.status === 429 && error.headers) {
['retry-after', 'x-ratelimit-remaining'].forEach(header => {
if (error.headers[header]) {
res.set(header, error.headers[header]);
}
});
}
res.status(error.status || 500).json({
success: false,
error: error.message,
requestId: req.headers['x-request-id']
});
}
});import {
ProxyConfig,
ProxyError,
ProxyResponse,
RequestWithLocals,
ErrorHandler,
ErrorHandlerHook,
ResponseHandler
} from 'express-simple-proxy';import {
urlJoin,
replaceUrlTemplate,
buildQueryString,
createFormDataPayload,
generateCurlCommand,
asyncWrapper
} from 'express-simple-proxy';
// URL manipulation
const url = urlJoin('https://api.example.com', 'users', '?page=1');
const templated = replaceUrlTemplate('/users/:id', { id: 123 });
// Query string building
const qs = buildQueryString({ page: 1, tags: ['red', 'blue'] });
// Form data creation
const formData = createFormDataPayload(req);
// Debug curl generation
const curlCommand = generateCurlCommand(payload, req);
// Async wrapper for middleware
const wrappedMiddleware = asyncWrapper(async (req, res, next) => {
// Your async logic
});- Total Coverage: 93.18%
- Tests Passed: 109/109 β
- Test Suites: Unit, Integration, Utils, Omitted Path
npm test # Run all tests
npm test -- --coverage # With coverage report
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
npm run test:watch # Watch modenpm install # Install dependencies
npm run build # Build the project
npm run dev # Development mode
npm run lint # Lint code
npm run format # Format codenpm run example # Basic usage
npm run example:omitted-path # Omitted path patterns
npm run example:api-gateway # Real-world API Gateway- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- FAQ - Common questions and solutions
- Examples - Practical usage examples
- Issues - Bug reports and feature requests
MIT License - see LICENSE file for details.
Made with β€οΈ by Nadim Tuhin