
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
express-simple-proxy
Advanced tools
A simple and powerful Express.js proxy middleware with TypeScript support, comprehensive error handling, and request/response transformation capabilities
A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.
Unlike general-purpose proxies, this package is specifically designed for API-to-API communication in modern web applications:
| Feature | Express Simple Proxy | http-proxy-middleware | express-http-proxy | node-http-proxy |
|---|---|---|---|---|
| Primary Use Case | API-to-API communication | General HTTP proxying | HTTP request proxying | Low-level HTTP proxy |
| TypeScript Support | ✅ Native & Complete | ⚠️ Types available | ⚠️ Types available | ❌ Limited |
| File Upload Handling | ✅ Built-in multipart/form-data | ❌ Manual setup required | ⚠️ Basic support | ❌ Not supported |
| JSON API Focus | ✅ Optimized for REST APIs | ⚠️ Generic proxy | ⚠️ Generic proxy | ❌ Low-level |
| Error Handling | ✅ Comprehensive with hooks | ⚠️ Basic | ⚠️ Basic | ❌ Manual |
| Request/Response Transform | ✅ Built-in handlers | ✅ Supported | ✅ Supported | ⚠️ Manual |
| Debugging Support | ✅ Curl generation | ❌ None | ❌ None | ❌ None |
| Setup Complexity | 🟢 Simple | 🟡 Moderate | 🟡 Moderate | 🔴 Complex |
✅ Choose Express Simple Proxy when:
❌ Consider alternatives when:
http-proxy-middleware)node-http-proxy)// Express Simple Proxy - Built for modern API architecture
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, maintainable API gateway
app.get('/api/users/:id', userService('/users/:id'));
app.get('/api/orders/:id', orderService('/orders/:id'));
app.post('/api/orders', orderService('/orders'));
npm install express-simple-proxy
import express from 'express';
import { createProxyController } from 'express-simple-proxy';
const app = express();
// Create proxy controller
const proxy = createProxyController({
baseURL: 'https://api.example.com',
headers: (req) => ({
'Authorization': `Bearer ${req.headers.authorization}`,
'User-Agent': 'MyApp/1.0'
})
});
// Use proxy middleware
app.get('/users', proxy('/api/users'));
app.get('/users/:id', proxy('/api/users/:id'));
app.post('/users', proxy('/api/users'));
app.listen(3000, () => {
console.log('Proxy server running on port 3000');
});
import { createProxyController, ProxyConfig } from 'express-simple-proxy';
const config: ProxyConfig = {
baseURL: 'https://api.example.com',
headers: (req) => ({
'Authorization': `Bearer ${req.locals.token}`,
'Content-Type': 'application/json'
}),
timeout: 30000, // 30 seconds
responseHeaders: (response) => ({
'X-Proxy-Response': 'true',
'X-Response-Time': Date.now().toString()
}),
errorHandler: (error, req, res) => {
res.status(error.status || 500).json({
success: false,
error: error.message,
timestamp: new Date().toISOString()
});
},
errorHandlerHook: async (error, req, res) => {
// Log error to external service
await logErrorToService(error, req);
// Add context to error
error.context = `${req.method} ${req.path}`;
return error;
}
};
const proxy = createProxyController(config);
| 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 |
// 1. Omitted proxy path - uses request path directly
app.get('/api/users', proxy());
// GET /api/users → https://api.example.com/api/users
app.post('/api/users', proxy());
// POST /api/users → https://api.example.com/api/users
app.get('/api/users/:id', proxy());
// GET /api/users/123 → https://api.example.com/api/users/123
// 2. Explicit proxy path - redirects to specific endpoint
app.get('/users', proxy('/api/users'));
// GET /users → https://api.example.com/api/users
app.get('/profile', proxy('/api/users/me'));
// GET /profile → https://api.example.com/api/users/me
// 3. Path parameter transformation
app.get('/users/:id', proxy('/api/users/:id'));
// GET /users/123 → https://api.example.com/api/users/123
app.get('/users/:userId/orders/:orderId', proxy('/api/users/:userId/orders/:orderId'));
// GET /users/123/orders/456 → https://api.example.com/api/users/123/orders/456
// 4. Path mapping - different frontend and backend paths
app.get('/dashboard/users', proxy('/api/admin/users'));
// GET /dashboard/users → https://api.example.com/api/admin/users
app.get('/public/health', proxy('/internal/health-check'));
// GET /public/health → https://api.example.com/internal/health-check
// 1. REST API Gateway Pattern
const userAPI = createProxyController({
baseURL: 'https://user-service.internal',
headers: (req) => ({ 'Authorization': req.headers.authorization })
});
// Omitted paths - direct mapping
app.get('/api/users', userAPI()); // → /api/users
app.post('/api/users', userAPI()); // → /api/users
app.get('/api/users/:id', userAPI()); // → /api/users/:id
app.put('/api/users/:id', userAPI()); // → /api/users/:id
app.delete('/api/users/:id', userAPI()); // → /api/users/:id
// 2. Service-to-Service Communication
const orderService = createProxyController({
baseURL: 'https://order-service.internal',
headers: (req) => ({ 'Service-Token': process.env.SERVICE_TOKEN })
});
// Direct service calls
app.get('/orders', orderService()); // → /orders
app.get('/orders/:id', orderService()); // → /orders/:id
app.post('/orders', orderService()); // → /orders
// 3. API Version Mapping
app.get('/v1/users', proxy('/api/v1/users'));
app.get('/v2/users', proxy('/api/v2/users'));
app.get('/latest/users', proxy('/api/v3/users')); // Latest maps to v3
// 4. Multi-Service Aggregation
const services = {
users: createProxyController({ baseURL: 'https://user-service.internal', headers: authHeaders }),
orders: createProxyController({ baseURL: 'https://order-service.internal', headers: authHeaders }),
products: createProxyController({ baseURL: 'https://product-service.internal', headers: authHeaders })
};
// Omitted paths for clean service routing
app.use('/api/users', services.users());
app.use('/api/orders', services.orders());
app.use('/api/products', services.products());
// 5. Dynamic Path Routing
app.get('/tenant/:tenantId/users', proxy('/api/tenants/:tenantId/users'));
app.get('/tenant/:tenantId/users/:userId', proxy('/api/tenants/:tenantId/users/:userId'));
When you omit the proxy path parameter, the middleware uses the original request path, making it perfect for direct API passthrough scenarios:
const proxy = createProxyController({
baseURL: 'https://api.backend.com',
headers: (req) => ({ 'Authorization': req.headers.authorization })
});
// Direct passthrough - request path matches backend path exactly
app.get('/api/users', proxy()); // GET /api/users → https://api.backend.com/api/users
app.post('/api/users', proxy()); // POST /api/users → https://api.backend.com/api/users
app.get('/api/users/:id', proxy()); // GET /api/users/123 → https://api.backend.com/api/users/123
app.put('/api/users/:id', proxy()); // PUT /api/users/123 → https://api.backend.com/api/users/123
app.delete('/api/users/:id', proxy()); // DELETE /api/users/123 → https://api.backend.com/api/users/123
// Perfect for REST API proxying
app.get('/api/orders', proxy());
app.post('/api/orders', proxy());
app.get('/api/orders/:id', proxy());
app.put('/api/orders/:id', proxy());
app.delete('/api/orders/:id', proxy());
// Handles complex nested paths automatically
app.get('/api/users/:userId/orders/:orderId', proxy());
// → https://api.backend.com/api/users/123/orders/456
Benefits of Omitted Proxy Path:
When to Use Omitted Proxy Path:
// 1. E-Commerce API Gateway
const productService = createProxyController({
baseURL: 'https://product-service.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Request-ID': req.headers['x-request-id'],
})
});
const orderService = createProxyController({
baseURL: 'https://order-service.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-User-ID': req.user?.id,
})
});
// Direct passthrough - no path transformation needed
app.get('/api/products', productService()); // → /api/products
app.get('/api/products/:id', productService()); // → /api/products/:id
app.post('/api/products/:id/reviews', productService()); // → /api/products/:id/reviews
app.get('/api/orders', orderService()); // → /api/orders
app.post('/api/orders', orderService()); // → /api/orders
app.get('/api/orders/:id/tracking', orderService()); // → /api/orders/:id/tracking
// 2. Multi-Tenant SaaS Application
const tenantProxy = createProxyController({
baseURL: 'https://tenant-api.saas.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Tenant-ID': req.params.tenantId,
'Content-Type': 'application/json'
})
});
// All tenant-specific routes use direct mapping
app.get('/api/tenants/:tenantId/users', tenantProxy());
app.post('/api/tenants/:tenantId/users', tenantProxy());
app.get('/api/tenants/:tenantId/users/:userId', tenantProxy());
app.put('/api/tenants/:tenantId/users/:userId', tenantProxy());
app.get('/api/tenants/:tenantId/analytics/dashboard', tenantProxy());
app.get('/api/tenants/:tenantId/billing/invoices', tenantProxy());
// 3. Development Environment Proxy
const devApiProxy = createProxyController({
baseURL: process.env.API_BASE_URL || 'https://api-dev.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'X-Environment': 'development',
'X-Developer': req.headers['x-developer-id'],
})
});
// Mirror production API structure exactly
app.use('/api', (req, res, next) => {
// Add development-specific middleware
console.log(`[DEV] ${req.method} ${req.path}`);
next();
});
app.get('/api/*', devApiProxy()); // Catch-all for GET requests
app.post('/api/*', devApiProxy()); // Catch-all for POST requests
app.put('/api/*', devApiProxy()); // Catch-all for PUT requests
app.delete('/api/*', devApiProxy()); // Catch-all for DELETE requests
// 4. GraphQL and REST API Bridge
const graphqlService = createProxyController({
baseURL: 'https://graphql-api.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'Content-Type': 'application/json',
})
});
const restService = createProxyController({
baseURL: 'https://rest-api.company.com',
headers: (req) => ({
'Authorization': req.headers.authorization,
'Accept': 'application/json',
})
});
// GraphQL endpoint
app.post('/graphql', graphqlService());
// REST endpoints - direct mapping
app.get('/api/v1/users', restService());
app.post('/api/v1/users', restService());
app.get('/api/v1/users/:id', restService());
app.put('/api/v1/users/:id', restService());
app.delete('/api/v1/users/:id', restService());
// 5. Microservices with Service Mesh
const 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');
const auditService = createServiceProxy('audit-service');
// Service mesh routing - paths stay consistent
app.get('/api/users', userService());
app.get('/api/users/:id/notifications', notificationService());
app.post('/api/audit/events', auditService());
// 6. API Versioning with Omitted Paths
const v1Service = createProxyController({
baseURL: 'https://api-v1.company.com',
headers: (req) => ({ 'API-Version': '1.0' })
});
const v2Service = createProxyController({
baseURL: 'https://api-v2.company.com',
headers: (req) => ({ 'API-Version': '2.0' })
});
// Version-specific routing with direct path mapping
app.use('/api/v1', (req, res, next) => {
// v1 specific middleware
next();
});
app.use('/api/v2', (req, res, next) => {
// v2 specific middleware
next();
});
// Direct mapping preserves API structure
app.get('/api/v1/users', v1Service());
app.get('/api/v1/users/:id', v1Service());
app.get('/api/v2/users', v2Service());
app.get('/api/v2/users/:id', v2Service());
import multer from 'multer';
const upload = multer({ storage: multer.memoryStorage() });
// Single file upload
app.post('/upload', upload.single('file'), proxy('/api/upload'));
// Multiple file upload
app.post('/upload-multiple', upload.array('files'), proxy('/api/upload-multiple'));
// Form data with file
app.post('/profile', upload.single('avatar'), proxy('/api/profile'));
// Custom response transformation
app.get('/users', proxy('/api/users', (req, res, remoteResponse) => {
res.json({
success: true,
data: remoteResponse.data,
timestamp: new Date().toISOString()
});
}));
// Return raw response
app.get('/users', proxy('/api/users', true));
const proxy = createProxyController({
baseURL: 'https://api.example.com',
headers: (req) => ({ 'Authorization': req.headers.authorization }),
// Error processing hook
errorHandlerHook: async (error, req, res) => {
// Log to monitoring service
await monitoring.logError(error, {
method: req.method,
path: req.path,
userId: req.user?.id
});
// Send alert for server errors
if (error.status >= 500) {
await alerting.sendAlert({
title: 'API Proxy Error',
message: `${error.status}: ${error.message}`,
severity: 'high'
});
}
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
}
};
// 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(response);
}
});
The proxy provides comprehensive error handling with three types of errors:
Server responded with an error status. The proxy preserves the original status code and error data.
Request was made but no response was received (timeout, connection refused, DNS failures).
Error in request configuration (invalid URL, malformed data).
The package exports several utility functions for advanced usage:
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 for file uploads
const formData = createFormDataPayload(req);
// Generate curl command for debugging
const curlCommand = generateCurlCommand(payload, req);
// Async wrapper for Express middleware
const wrappedMiddleware = asyncWrapper(async (req, res, next) => {
// Your async middleware logic
});
The package is written in TypeScript and includes comprehensive type definitions:
import {
ProxyConfig,
ProxyError,
ProxyResponse,
RequestWithLocals,
ErrorHandler,
ErrorHandlerHook,
ResponseHandler
} 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) => {
// Type-safe error handling
res.status(error.status || 500).json({
error: error.message
});
}
};
The package includes comprehensive unit and integration tests with 93.18% coverage.
For common questions and troubleshooting, see the FAQ.
proxy.ts: 91.01% coveragetypes.ts: 100% coverageutils.ts: 95.23% coverage# Run all tests
npm test
# Run tests with coverage report
npm test -- --coverage
# Run unit tests only
npm run test:unit
# Run integration tests only
npm run test:integration
# Run tests in watch mode
npm run test:watch
# Install dependencies
npm install
# Build the project
npm run build
# Run in development mode
npm run dev
# Lint code
npm run lint
# Format code
npm run format
# Run examples
npm run example # Basic usage example
npm run example:basic # Basic proxy usage
npm run example:omitted-path # Comprehensive omitted path examples
npm run example:api-gateway # Real-world API Gateway example
The repository includes comprehensive examples demonstrating various use cases:
examples/basic-usage.ts)Simple proxy setup with basic configuration and usage patterns.
examples/omitted-path.ts)Comprehensive examples showcasing omitted proxy path patterns:
examples/api-gateway.ts)A complete, runnable API Gateway implementation featuring:
Quick Start with API Gateway:
# Start the API Gateway
npm run example:api-gateway
# Test endpoints
curl http://localhost:8080/api/docs # API documentation
curl http://localhost:8080/api/health # Health check
curl http://localhost:8080/posts # Public posts via JSONPlaceholder
# Authentication
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password"}'
# Use authenticated endpoints
curl http://localhost:8080/api/users \
-H "Authorization: Bearer YOUR_TOKEN"
Contributions are welcome! Please read our Contributing Guide for details on how to contribute to this project.
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for a detailed history of changes to this project.
If you encounter any issues or have questions, please:
Made with ❤️ by Nadim Tuhin
FAQs
A simple and powerful Express.js proxy middleware with TypeScript support, comprehensive error handling, and request/response transformation capabilities
We found that express-simple-proxy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.