Skip to content

A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.

License

Notifications You must be signed in to change notification settings

nadimtuhin/express-simple-proxy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

32 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Express Simple Proxy

npm version Build Status Coverage Tests License: MIT TypeScript Known Vulnerabilities

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.

npm install express-simple-proxy

Quick Start

1. Basic Proxy

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

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

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:

// 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:

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

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

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

// 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)

// 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

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

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

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

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 for detailed recipes.

Quick Examples

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)

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

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

import {
  ProxyConfig,
  ProxyError,
  ProxyResponse,
  RequestWithLocals,
  ErrorHandler,
  ErrorHandlerHook,
  ResponseHandler
} from 'express-simple-proxy';

Utility Functions

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

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

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

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 - Common questions and solutions
  • Examples - Practical usage examples
  • Issues - Bug reports and feature requests

License

MIT License - see LICENSE file for details.


Made with ❀️ by Nadim Tuhin

About

A simple, powerful, and TypeScript-ready Express.js proxy middleware with comprehensive error handling, request/response transformation, and file upload support.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •