{"id":31592610,"url":"https://github.com/nadimtuhin/express-simple-proxy","last_synced_at":"2025-10-06T03:14:46.842Z","repository":{"id":304868603,"uuid":"1020310132","full_name":"nadimtuhin/express-simple-proxy","owner":"nadimtuhin","description":"A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.","archived":false,"fork":false,"pushed_at":"2025-07-19T19:34:18.000Z","size":203,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-11T13:53:32.910Z","etag":null,"topics":["api-gateway","express","expressjs","http","middleware","nodejs","proxy","request-transformation","response-transformation","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nadimtuhin.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-15T17:02:43.000Z","updated_at":"2025-07-23T02:11:31.000Z","dependencies_parsed_at":"2025-07-16T20:50:30.441Z","dependency_job_id":"1d0682a6-2373-418b-8656-7da18d98c70e","html_url":"https://github.com/nadimtuhin/express-simple-proxy","commit_stats":null,"previous_names":["nadimtuhin/express-simple-proxy"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nadimtuhin/express-simple-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fexpress-simple-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fexpress-simple-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fexpress-simple-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fexpress-simple-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nadimtuhin","download_url":"https://codeload.github.com/nadimtuhin/express-simple-proxy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nadimtuhin%2Fexpress-simple-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278551834,"owners_count":26005457,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-gateway","express","expressjs","http","middleware","nodejs","proxy","request-transformation","response-transformation","typescript"],"created_at":"2025-10-06T03:11:36.509Z","updated_at":"2025-10-06T03:14:46.835Z","avatar_url":"https://github.com/nadimtuhin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Express Simple Proxy\n\n[![npm version](https://badge.fury.io/js/express-simple-proxy.svg)](https://badge.fury.io/js/express-simple-proxy)\n[![Build Status](https://github.com/nadimtuhin/express-simple-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/nadimtuhin/express-simple-proxy/actions)\n[![Coverage](https://img.shields.io/badge/coverage-93.18%25-brightgreen)](https://github.com/nadimtuhin/express-simple-proxy/actions)\n[![Tests](https://img.shields.io/badge/tests-109%20passed-brightgreen)](https://github.com/nadimtuhin/express-simple-proxy/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/)\n[![Known Vulnerabilities](https://snyk.io/test/github/nadimtuhin/express-simple-proxy/badge.svg)](https://snyk.io/test/github/nadimtuhin/express-simple-proxy)\n\nTypeScript-ready Express middleware for proxying API requests with zero configuration needed. Perfect for API gateways and microservices.\n\n**⚠️ Note:** This package handles HTTP requests only. For WebSocket proxying, use dedicated WebSocket proxy solutions like `http-proxy-middleware`.\n\n```bash\nnpm install express-simple-proxy\n```\n\n## Quick Start\n\n### 1. Basic Proxy\n```typescript\nimport express from 'express';\nimport { createProxyController } from 'express-simple-proxy';\n\nconst app = express();\n\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com'\n});\n\n// Direct path mapping - no configuration needed\napp.get('/users', proxy());\napp.post('/users', proxy());\napp.get('/users/:id', proxy());\n```\n\n### 2. With Authentication\n```typescript\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com',\n  headers: (req) =\u003e ({\n    'Authorization': `Bearer ${req.headers.authorization}`,\n    'User-Agent': 'MyApp/1.0'\n  })\n});\n\napp.use('/api', proxy()); // Proxy all /api/* routes\n```\n\n### 3. With Error Handling\n```typescript\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com',\n  headers: (req) =\u003e ({ 'Authorization': req.headers.authorization }),\n  errorHandler: (error, req, res) =\u003e {\n    res.status(error.status || 500).json({\n      success: false,\n      error: error.message,\n      timestamp: new Date().toISOString()\n    });\n  }\n});\n```\n\n## Core Concepts\n\n### Omitted Path Pattern\nThe key differentiator is **omitted proxy paths** - when you call `proxy()` without a path parameter, it uses the original request path:\n\n```typescript\n// Traditional proxy libraries require explicit path mapping:\napp.get('/users', proxy('/api/users'));        // Maps /users → /api/users\napp.get('/users/:id', proxy('/api/users/:id')); // Maps /users/123 → /api/users/123\n\n// Express Simple Proxy - zero configuration:\napp.get('/users', proxy());                     // Maps /users → /users\napp.get('/users/:id', proxy());                 // Maps /users/123 → /users/123\n```\n\n**Benefits:**\n- ✅ **Zero Configuration**: No path mapping needed\n- ✅ **Consistent Routing**: Frontend and backend paths stay in sync\n- ✅ **Automatic Parameter Handling**: All path parameters are preserved\n- ✅ **Perfect for Microservices**: Direct service-to-service communication\n\n### TypeScript-First Approach\nBuilt from the ground up with TypeScript, not retrofitted:\n\n```typescript\nimport { ProxyConfig, ProxyError, RequestWithLocals } from 'express-simple-proxy';\n\nconst config: ProxyConfig = {\n  baseURL: 'https://api.example.com',\n  headers: (req: RequestWithLocals) =\u003e ({\n    'Authorization': `Bearer ${req.locals?.token}`\n  }),\n  errorHandler: (error: ProxyError, req: RequestWithLocals, res: Response) =\u003e {\n    // Full type safety throughout\n  }\n};\n```\n\n### API-Focused vs General HTTP Proxy\nOptimized specifically for REST API communication:\n\n| Feature | Express Simple Proxy | General HTTP Proxies |\n|---------|---------------------|---------------------|\n| **JSON APIs** | ✅ Optimized handling | ⚠️ Generic support |\n| **File Uploads** | ✅ Built-in multipart/form-data | ❌ Manual setup |\n| **Error Processing** | ✅ Structured error hooks | ⚠️ Basic forwarding |\n| **TypeScript** | ✅ Native \u0026 complete | ⚠️ Addon types |\n| **Setup Complexity** | 🟢 Minimal | 🟡 Configuration heavy |\n\n## Configuration\n\n### Configuration Options\n\n| Option | Type | Required | Description |\n|--------|------|----------|-------------|\n| `baseURL` | `string` | ✅ | Base URL for the target API |\n| `headers` | `function` | ✅ | Function that returns headers object based on request |\n| `timeout` | `number` | ❌ | Request timeout in milliseconds (default: 30000) |\n| `responseHeaders` | `function` | ❌ | Function to transform response headers |\n| `errorHandler` | `function` | ❌ | Custom error handling function |\n| `errorHandlerHook` | `function` | ❌ | Error processing hook function |\n\n### Advanced Configuration\n\n```typescript\nconst config: ProxyConfig = {\n  baseURL: 'https://api.example.com',\n  timeout: 30000,\n  \n  headers: (req) =\u003e ({\n    'Authorization': `Bearer ${req.locals.token}`,\n    'Content-Type': 'application/json',\n    'X-Request-ID': req.headers['x-request-id']\n  }),\n  \n  responseHeaders: (response) =\u003e ({\n    'X-Proxy-Response': 'true',\n    'X-Response-Time': Date.now().toString()\n  }),\n  \n  // Error processing hook - runs before error handler\n  errorHandlerHook: async (error, req, res) =\u003e {\n    // Log to monitoring service\n    await logErrorToService(error, req);\n    \n    // Add context to error\n    error.context = `${req.method} ${req.path}`;\n    return error;\n  },\n  \n  // Custom error response\n  errorHandler: (error, req, res) =\u003e {\n    const response = {\n      success: false,\n      error: {\n        message: error.message,\n        code: error.code,\n        status: error.status\n      },\n      meta: {\n        timestamp: new Date().toISOString(),\n        requestId: req.headers['x-request-id'],\n        path: req.path\n      }\n    };\n    \n    res.status(error.status || 500).json(response);\n  }\n};\n```\n\n## Advanced Usage\n\n### File Upload Proxy\n```typescript\nimport multer from 'multer';\n\nconst upload = multer({ storage: multer.memoryStorage() });\n\n// Single file upload\napp.post('/upload', upload.single('file'), proxy());\n\n// Multiple file upload\napp.post('/upload-multiple', upload.array('files'), proxy());\n\n// Form data with file\napp.post('/profile', upload.single('avatar'), proxy());\n```\n\n### Custom Response Transformation\n```typescript\n// Transform response data\napp.get('/users', proxy(undefined, (req, res, remoteResponse) =\u003e {\n  res.json({\n    success: true,\n    data: remoteResponse.data,\n    timestamp: new Date().toISOString()\n  });\n}));\n\n// Return raw response\napp.get('/raw-data', proxy(undefined, true));\n```\n\n### Path Mapping (When Needed)\n```typescript\n// Explicit path mapping for different frontend/backend structures\napp.get('/dashboard/users', proxy('/api/admin/users'));\napp.get('/public/health', proxy('/internal/health-check'));\n\n// API version mapping\napp.get('/v1/users', proxy('/api/v1/users'));\napp.get('/latest/users', proxy('/api/v3/users'));\n```\n\n## Use Cases \u0026 Examples\n\n### API Gateway Pattern\n```typescript\nconst userService = createProxyController({\n  baseURL: 'https://user-service.internal',\n  headers: (req) =\u003e ({ 'Authorization': req.headers.authorization })\n});\n\nconst orderService = createProxyController({\n  baseURL: 'https://order-service.internal',\n  headers: (req) =\u003e ({ 'Authorization': req.headers.authorization })\n});\n\n// Clean service routing with omitted paths\napp.get('/api/users', userService());\napp.post('/api/users', userService());\napp.get('/api/users/:id', userService());\n\napp.get('/api/orders', orderService());\napp.post('/api/orders', orderService());\napp.get('/api/orders/:id', orderService());\n```\n\n### Multi-Tenant SaaS\n```typescript\nconst tenantProxy = createProxyController({\n  baseURL: 'https://tenant-api.saas.com',\n  headers: (req) =\u003e ({\n    'Authorization': req.headers.authorization,\n    'X-Tenant-ID': req.params.tenantId\n  })\n});\n\n// All tenant routes use direct mapping\napp.get('/api/tenants/:tenantId/users', tenantProxy());\napp.get('/api/tenants/:tenantId/billing', tenantProxy());\napp.get('/api/tenants/:tenantId/analytics', tenantProxy());\n```\n\n### Development Environment Mirror\n```typescript\nconst devProxy = createProxyController({\n  baseURL: process.env.API_BASE_URL || 'https://api-dev.company.com',\n  headers: (req) =\u003e ({\n    'Authorization': req.headers.authorization,\n    'X-Environment': 'development'\n  })\n});\n\n// Mirror production API structure exactly\napp.use('/api', devProxy());  // Catch-all for all API routes\n```\n\n### Microservices with Service Discovery\n```typescript\nconst createServiceProxy = (serviceName: string) =\u003e {\n  return createProxyController({\n    baseURL: `https://${serviceName}.mesh.internal`,\n    headers: (req) =\u003e ({\n      'Authorization': req.headers.authorization,\n      'X-Correlation-ID': req.headers['x-correlation-id'] || generateId(),\n      'X-Service-Name': serviceName\n    })\n  });\n};\n\nconst userService = createServiceProxy('user-service');\nconst notificationService = createServiceProxy('notification-service');\n\n// Service mesh routing with consistent paths\napp.get('/api/users', userService());\napp.get('/api/notifications', notificationService());\n```\n\n## Cookbook\n\nA comprehensive collection of practical examples for common use cases. See the **[Complete Cookbook](./COOKBOOK.md)** for detailed recipes.\n\n### Quick Examples\n\n**Authentication \u0026 Security:**\n```typescript\n// JWT Token Forwarding\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com',\n  headers: (req) =\u003e ({\n    'Authorization': req.headers.authorization,\n    'X-User-ID': req.user?.id,\n    'X-Request-ID': crypto.randomUUID()\n  })\n});\n```\n\n**File Uploads:**\n```typescript\nimport multer from 'multer';\nconst upload = multer({ \n  storage: multer.memoryStorage(),\n  limits: { fileSize: 10 * 1024 * 1024 }\n});\n\napp.post('/upload', upload.single('file'), proxy('/api/upload'));\n```\n\n**Load Balancing:**\n```typescript\nconst servers = ['https://api1.com', 'https://api2.com', 'https://api3.com'];\nlet current = 0;\n\napp.use('/api', (req, res, next) =\u003e {\n  const selectedServer = servers[current++ % servers.length];\n  const proxy = createProxyController({\n    baseURL: selectedServer,\n    headers: (req) =\u003e ({ 'Authorization': req.headers.authorization })\n  });\n  proxy()(req, res, next);\n});\n```\n\n**Performance Monitoring:**\n```typescript\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com',\n  errorHandlerHook: async (error, req, res) =\u003e {\n    console.log({\n      method: req.method,\n      path: req.path,\n      duration: Date.now() - req.startTime,\n      status: error.status\n    });\n    return error;\n  }\n});\n```\n\n**📖 [View Complete Cookbook](./COOKBOOK.md)** - Contains 50+ recipes for:\n- Authentication \u0026 Security (JWT, API Keys, OAuth2)\n- File Handling (Uploads, Validation, Chunking)\n- Database \u0026 Caching (Sharding, Redis, Cache Control)\n- Monitoring \u0026 Observability (Tracing, Metrics, Health Checks)\n- Load Balancing \u0026 Failover (Round Robin, Circuit Breakers)\n- Development \u0026 Testing (Mocking, A/B Testing, Feature Flags)\n- Rate Limiting \u0026 Throttling (Basic, Per-user)\n- Data Transformation (Schema Validation, GraphQL)\n- Content Negotiation (Accept Headers, Compression)\n\n## Error Handling\n\n### Error Types\n1. **Response Errors (4xx/5xx)**: Server responded with error status\n2. **Network Errors (503)**: No response received (timeout, connection refused)  \n3. **Request Setup Errors (500)**: Invalid configuration or malformed data\n\n### Error Handler Flow\n1. **Error Occurs** → 2. **Error Hook Processing** → 3. **Error Handling** → 4. **Fallback**\n\n### Advanced Error Handling\n```typescript\nconst proxy = createProxyController({\n  baseURL: 'https://api.example.com',\n  \n  errorHandlerHook: async (error, req, res) =\u003e {\n    // Monitor and alert\n    await monitoring.logError(error, { method: req.method, path: req.path });\n    \n    if (error.status \u003e= 500) {\n      await alerting.sendAlert({\n        title: 'API Proxy Error',\n        severity: 'high'\n      });\n    }\n    \n    return error;\n  },\n  \n  errorHandler: (error, req, res) =\u003e {\n    // Forward rate limiting headers\n    if (error.status === 429 \u0026\u0026 error.headers) {\n      ['retry-after', 'x-ratelimit-remaining'].forEach(header =\u003e {\n        if (error.headers[header]) {\n          res.set(header, error.headers[header]);\n        }\n      });\n    }\n    \n    res.status(error.status || 500).json({\n      success: false,\n      error: error.message,\n      requestId: req.headers['x-request-id']\n    });\n  }\n});\n```\n\n## API Reference\n\n### Types\n```typescript\nimport {\n  ProxyConfig,\n  ProxyError,\n  ProxyResponse,\n  RequestWithLocals,\n  ErrorHandler,\n  ErrorHandlerHook,\n  ResponseHandler\n} from 'express-simple-proxy';\n```\n\n### Utility Functions\n```typescript\nimport {\n  urlJoin,\n  replaceUrlTemplate,\n  buildQueryString,\n  createFormDataPayload,\n  generateCurlCommand,\n  asyncWrapper\n} from 'express-simple-proxy';\n\n// URL manipulation\nconst url = urlJoin('https://api.example.com', 'users', '?page=1');\nconst templated = replaceUrlTemplate('/users/:id', { id: 123 });\n\n// Query string building\nconst qs = buildQueryString({ page: 1, tags: ['red', 'blue'] });\n\n// Form data creation\nconst formData = createFormDataPayload(req);\n\n// Debug curl generation\nconst curlCommand = generateCurlCommand(payload, req);\n\n// Async wrapper for middleware\nconst wrappedMiddleware = asyncWrapper(async (req, res, next) =\u003e {\n  // Your async logic\n});\n```\n\n## Development \u0026 Testing\n\n### Test Coverage\n- **Total Coverage**: 93.18%\n- **Tests Passed**: 109/109 ✅\n- **Test Suites**: Unit, Integration, Utils, Omitted Path\n\n### Running Tests\n```bash\nnpm test                    # Run all tests\nnpm test -- --coverage     # With coverage report\nnpm run test:unit          # Unit tests only\nnpm run test:integration   # Integration tests only\nnpm run test:watch         # Watch mode\n```\n\n### Development Commands\n```bash\nnpm install                # Install dependencies\nnpm run build              # Build the project\nnpm run dev                # Development mode\nnpm run lint               # Lint code\nnpm run format             # Format code\n```\n\n### Examples\n```bash\nnpm run example                # Basic usage\nnpm run example:omitted-path   # Omitted path patterns\nnpm run example:api-gateway    # Real-world API Gateway\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## Support\n\n- [FAQ](./FAQ.md) - Common questions and solutions\n- [Examples](./examples/) - Practical usage examples\n- [Issues](https://github.com/nadimtuhin/express-simple-proxy/issues) - Bug reports and feature requests\n\n## License\n\nMIT License - see [LICENSE](./LICENSE) file for details.\n\n---\n\nMade with ❤️ by [Nadim Tuhin](https://github.com/nadimtuhin)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadimtuhin%2Fexpress-simple-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnadimtuhin%2Fexpress-simple-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnadimtuhin%2Fexpress-simple-proxy/lists"}