https://github.com/nadimtuhin/express-simple-proxy
A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.
https://github.com/nadimtuhin/express-simple-proxy
api-gateway express expressjs http middleware nodejs proxy request-transformation response-transformation typescript
Last synced: 4 months ago
JSON representation
A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.
- Host: GitHub
- URL: https://github.com/nadimtuhin/express-simple-proxy
- Owner: nadimtuhin
- License: mit
- Created: 2025-07-15T17:02:43.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-07-19T19:34:18.000Z (7 months ago)
- Last Synced: 2025-09-11T13:53:32.910Z (5 months ago)
- Topics: api-gateway, express, expressjs, http, middleware, nodejs, proxy, request-transformation, response-transformation, typescript
- Language: TypeScript
- Homepage:
- Size: 198 KB
- Stars: 4
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Express Simple Proxy
[](https://badge.fury.io/js/express-simple-proxy)
[](https://github.com/nadimtuhin/express-simple-proxy/actions)
[](https://github.com/nadimtuhin/express-simple-proxy/actions)
[](https://github.com/nadimtuhin/express-simple-proxy/actions)
[](https://opensource.org/licenses/MIT)
[](https://www.typescriptlang.org/)
[](https://snyk.io/test/github/nadimtuhin/express-simple-proxy)
TypeScript-ready Express middleware for proxying API requests with zero configuration needed. Perfect for API gateways and microservices.
**⚠️ Note:** This package handles HTTP requests only. For WebSocket proxying, use dedicated WebSocket proxy solutions like `http-proxy-middleware`.
```bash
npm install express-simple-proxy
```
## Quick Start
### 1. Basic Proxy
```typescript
import 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());
```
### 2. With Authentication
```typescript
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/* routes
```
### 3. With Error Handling
```typescript
const 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()
});
}
});
```
## Core Concepts
### Omitted Path Pattern
The key differentiator is **omitted proxy paths** - when you call `proxy()` without a path parameter, it uses the original request path:
```typescript
// 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/123
```
**Benefits:**
- ✅ **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
### TypeScript-First Approach
Built from the ground up with TypeScript, not retrofitted:
```typescript
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
}
};
```
### API-Focused vs General HTTP Proxy
Optimized specifically for REST API communication:
| Feature | Express Simple Proxy | General HTTP Proxies |
|---------|---------------------|---------------------|
| **JSON APIs** | ✅ Optimized handling | ⚠️ Generic support |
| **File Uploads** | ✅ Built-in multipart/form-data | ❌ Manual setup |
| **Error Processing** | ✅ Structured error hooks | ⚠️ Basic forwarding |
| **TypeScript** | ✅ Native & complete | ⚠️ Addon types |
| **Setup Complexity** | 🟢 Minimal | 🟡 Configuration heavy |
## Configuration
### Configuration Options
| 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 |
### Advanced Configuration
```typescript
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);
}
};
```
## Advanced Usage
### File Upload Proxy
```typescript
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());
```
### Custom Response Transformation
```typescript
// 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));
```
### Path Mapping (When Needed)
```typescript
// 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'));
```
## Use Cases & Examples
### API Gateway Pattern
```typescript
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());
```
### Multi-Tenant SaaS
```typescript
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());
```
### Development Environment Mirror
```typescript
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 routes
```
### Microservices with Service Discovery
```typescript
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');
// Service mesh routing with consistent paths
app.get('/api/users', userService());
app.get('/api/notifications', notificationService());
```
## Cookbook
A comprehensive collection of practical examples for common use cases. See the **[Complete Cookbook](./COOKBOOK.md)** for detailed recipes.
### Quick Examples
**Authentication & Security:**
```typescript
// 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:**
```typescript
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:**
```typescript
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:**
```typescript
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](./COOKBOOK.md)** - 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)
## Error Handling
### Error Types
1. **Response Errors (4xx/5xx)**: Server responded with error status
2. **Network Errors (503)**: No response received (timeout, connection refused)
3. **Request Setup Errors (500)**: Invalid configuration or malformed data
### Error Handler Flow
1. **Error Occurs** → 2. **Error Hook Processing** → 3. **Error Handling** → 4. **Fallback**
### Advanced Error Handling
```typescript
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']
});
}
});
```
## API Reference
### Types
```typescript
import {
ProxyConfig,
ProxyError,
ProxyResponse,
RequestWithLocals,
ErrorHandler,
ErrorHandlerHook,
ResponseHandler
} from 'express-simple-proxy';
```
### Utility Functions
```typescript
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
});
```
## Development & Testing
### Test Coverage
- **Total Coverage**: 93.18%
- **Tests Passed**: 109/109 ✅
- **Test Suites**: Unit, Integration, Utils, Omitted Path
### Running Tests
```bash
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 mode
```
### Development Commands
```bash
npm install # Install dependencies
npm run build # Build the project
npm run dev # Development mode
npm run lint # Lint code
npm run format # Format code
```
### Examples
```bash
npm run example # Basic usage
npm run example:omitted-path # Omitted path patterns
npm run example:api-gateway # Real-world API Gateway
```
## Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## Support
- [FAQ](./FAQ.md) - Common questions and solutions
- [Examples](./examples/) - Practical usage examples
- [Issues](https://github.com/nadimtuhin/express-simple-proxy/issues) - Bug reports and feature requests
## License
MIT License - see [LICENSE](./LICENSE) file for details.
---
Made with ❤️ by [Nadim Tuhin](https://github.com/nadimtuhin)