loadbalancing
v0.1.0
Published
Configurable local load balancer for development, testing, and E2E verification of distributed systems
Downloads
7
Maintainers
Readme
loadbalancing
Configurable local load balancer for development, testing, and E2E verification of distributed systems, APIs, and services.
Features
- 🔄 Multiple Strategies - Round-robin, least connections, random, weighted, IP hash
- ❤️ Health Checking - Automatic backend health monitoring with configurable thresholds
- 🔌 HTTP & WebSocket - Full support for HTTP and WebSocket proxying
- 📊 Statistics - Real-time metrics and connection tracking
- 🎯 Sticky Sessions - Session persistence by client IP
- 🚀 Dynamic Backends - Add/remove backends at runtime
- 📡 Event System - Listen to health changes, requests, and errors
- ⚡ Zero Config - Works out of the box with sensible defaults
- 🧪 Testing Ready - Perfect for E2E tests and local development
- 🎯 TypeScript - Full type definitions included
Installation
npm install loadbalancingQuick Start
Basic Usage
import { createLoadBalancer } from 'loadbalancing';
// Create load balancer
const lb = createLoadBalancer({
backends: [
{ id: 'server1', host: 'localhost', port: 3001 },
{ id: 'server2', host: 'localhost', port: 3002 },
{ id: 'server3', host: 'localhost', port: 3003 },
],
strategy: 'round-robin',
});
// Start on port 3000
await lb.start(3000);
console.log('Load balancer running on port 3000');
// Stop when done
await lb.stop();With Health Checking
import { createLoadBalancer } from 'loadbalancing';
const lb = createLoadBalancer({
backends: [
{ id: 'api1', host: 'localhost', port: 4001 },
{ id: 'api2', host: 'localhost', port: 4002 },
],
strategy: 'least-connections',
healthCheck: {
enabled: true,
interval: 5000, // Check every 5 seconds
timeout: 2000, // 2 second timeout
path: '/health', // Health check endpoint
unhealthyThreshold: 3, // 3 failures = unhealthy
healthyThreshold: 2, // 2 successes = healthy
},
});
// Listen to health changes
lb.on('health-change', (backend) => {
console.log(`Backend ${backend.id} is now ${backend.healthy ? 'healthy' : 'unhealthy'}`);
});
await lb.start(8080);Weighted Load Balancing
const lb = createLoadBalancer({
backends: [
{ id: 'powerful', host: 'localhost', port: 5001, weight: 3 },
{ id: 'medium', host: 'localhost', port: 5002, weight: 2 },
{ id: 'light', host: 'localhost', port: 5003, weight: 1 },
],
strategy: 'weighted',
});
await lb.start(9000);
// Requests distributed 3:2:1 across backendsSticky Sessions
const lb = createLoadBalancer({
backends: [
{ id: 'ws1', host: 'localhost', port: 6001 },
{ id: 'ws2', host: 'localhost', port: 6002 },
],
strategy: 'round-robin',
stickySession: true, // Enable sticky sessions
sessionTimeout: 600000, // 10 minutes
});
await lb.start(7000);
// Same client IP always routes to same backendLoad Balancing Strategies
Round Robin
Distributes requests evenly across all healthy backends in rotation.
{ strategy: 'round-robin' }Least Connections
Routes to the backend with the fewest active connections.
{ strategy: 'least-connections' }Random
Randomly selects a healthy backend for each request.
{ strategy: 'random' }Weighted
Distributes requests based on backend weights (higher weight = more requests).
{
strategy: 'weighted',
backends: [
{ id: 'b1', host: 'localhost', port: 3001, weight: 5 },
{ id: 'b2', host: 'localhost', port: 3002, weight: 3 },
{ id: 'b3', host: 'localhost', port: 3003, weight: 2 },
]
}IP Hash
Consistent hashing based on client IP (useful for session affinity).
{ strategy: 'ip-hash' }Dynamic Backend Management
Add and remove backends at runtime:
const lb = createLoadBalancer({
backends: [
{ id: 'initial', host: 'localhost', port: 8001 },
],
strategy: 'round-robin',
});
await lb.start(3000);
// Add new backend
lb.addBackend({ id: 'dynamic1', host: 'localhost', port: 8002 });
// Listen for changes
lb.on('backend-added', (backend) => {
console.log(`Added backend: ${backend.id}`);
});
lb.on('backend-removed', (backendId) => {
console.log(`Removed backend: ${backendId}`);
});
// Remove backend
lb.removeBackend('dynamic1');Statistics and Monitoring
Get real-time statistics:
const stats = lb.getStats();
console.log(stats);
/*
{
totalRequests: 1500,
activeConnections: 23,
backends: [
{ id: 'server1', healthy: true, connections: 8, totalRequests: 500 },
{ id: 'server2', healthy: true, connections: 7, totalRequests: 480 },
{ id: 'server3', healthy: false, connections: 0, totalRequests: 520 }
]
}
*/Event System
Listen to load balancer events:
// Request received
lb.on('request', ({ backend, clientIp, url }) => {
console.log(`Request from ${clientIp} to ${backend.id}: ${url}`);
});
// WebSocket upgrade
lb.on('websocket', ({ backend, clientIp, url }) => {
console.log(`WebSocket from ${clientIp} to ${backend.id}: ${url}`);
});
// Health status changed
lb.on('health-change', (backend) => {
console.log(`${backend.id}: ${backend.healthy ? 'UP' : 'DOWN'}`);
});
// No healthy backends available
lb.on('no-backend', ({ clientIp, url }) => {
console.log(`No backend available for ${clientIp}: ${url}`);
});
// Proxy error
lb.on('proxy-error', ({ error, backend, url }) => {
console.error(`Proxy error for ${backend.id}: ${error.message}`);
});
// Load balancer started
lb.on('start', ({ port, host }) => {
console.log(`Load balancer started on ${host}:${port}`);
});
// Load balancer stopped
lb.on('stop', () => {
console.log('Load balancer stopped');
});E2E Testing Example
Perfect for testing multi-instance scenarios:
import { createLoadBalancer } from 'loadbalancing';
import { spawn } from 'child_process';
describe('Multi-instance E2E tests', () => {
let lb;
let instances = [];
beforeAll(async () => {
// Start 3 backend instances
for (let i = 0; i < 3; i++) {
const port = 4000 + i;
const instance = spawn('node', ['server.js', '--port', port]);
instances.push(instance);
await waitForServer(port);
}
// Start load balancer
lb = createLoadBalancer({
backends: [
{ id: 'instance1', host: 'localhost', port: 4000 },
{ id: 'instance2', host: 'localhost', port: 4001 },
{ id: 'instance3', host: 'localhost', port: 4002 },
],
strategy: 'round-robin',
});
await lb.start(5000);
});
afterAll(async () => {
await lb.stop();
instances.forEach(i => i.kill());
});
test('distributes load across instances', async () => {
// Make requests to load balancer
for (let i = 0; i < 30; i++) {
await fetch('http://localhost:5000/api/test');
}
// Verify distribution
const stats = lb.getStats();
stats.backends.forEach(backend => {
expect(backend.totalRequests).toBeGreaterThan(5);
expect(backend.totalRequests).toBeLessThan(15);
});
});
test('handles instance failure gracefully', async () => {
// Kill one instance
instances[1].kill();
await new Promise(resolve => setTimeout(resolve, 6000)); // Wait for health check
// Verify only healthy backends receive traffic
const stats = lb.getStats();
const unhealthy = stats.backends.filter(b => !b.healthy);
expect(unhealthy).toHaveLength(1);
});
});Development Server Example
Use as a development load balancer:
// dev-loadbalancer.ts
import { createLoadBalancer } from 'loadbalancing';
const lb = createLoadBalancer({
backends: [
{ id: 'dev1', host: 'localhost', port: 3001 },
{ id: 'dev2', host: 'localhost', port: 3002 },
],
strategy: 'round-robin',
healthCheck: {
enabled: true,
interval: 3000,
path: '/health',
},
});
lb.on('request', ({ backend, url }) => {
console.log(`[${new Date().toISOString()}] ${backend.id} <- ${url}`);
});
lb.on('health-change', (backend) => {
const status = backend.healthy ? '✓ UP' : '✗ DOWN';
console.log(`[${backend.id}] ${status}`);
});
await lb.start(8000, 'localhost');
console.log('Development load balancer running on http://localhost:8000');Run with:
npx tsx dev-loadbalancer.tsAPI Reference
createLoadBalancer(config)
Creates a new load balancer instance.
Parameters:
config: LoadBalancerConfig- Configuration objectbackends: Backend[]- Array of backend servers (required)strategy?: BalancingStrategy- Load balancing strategy (default: 'round-robin')healthCheck?: HealthCheckConfig- Health check configurationstickySession?: boolean- Enable sticky sessions (default: false)sessionTimeout?: number- Session timeout in ms (default: 600000)
Returns: LoadBalancer
LoadBalancer
Main load balancer class.
Methods:
start(port: number, host?: string): Promise<void>- Start the load balancerstop(): Promise<void>- Stop the load balanceraddBackend(backend: Backend): void- Add a backend dynamicallyremoveBackend(backendId: string): void- Remove a backendgetStats(): LoadBalancerStats- Get statisticsgetBackends(): BackendWithHealth[]- Get all backends with health statusisRunning(): boolean- Check if running
Events:
start- Load balancer startedstop- Load balancer stoppedrequest- HTTP request receivedwebsocket- WebSocket connectionhealth-change- Backend health changedno-backend- No healthy backend availableproxy-error- Proxy error occurredbackend-added- Backend addedbackend-removed- Backend removed
Types
interface Backend {
id: string;
host: string;
port: number;
weight?: number;
metadata?: Record<string, unknown>;
}
interface BackendWithHealth extends Backend {
healthy: boolean;
connections: number;
lastHealthCheck?: Date;
consecutiveFailures: number;
}
type BalancingStrategy =
| 'round-robin'
| 'least-connections'
| 'random'
| 'weighted'
| 'ip-hash';
interface HealthCheckConfig {
enabled?: boolean; // default: true
interval?: number; // default: 5000ms
timeout?: number; // default: 2000ms
path?: string; // default: '/'
unhealthyThreshold?: number; // default: 3
healthyThreshold?: number; // default: 2
}
interface LoadBalancerStats {
totalRequests: number;
activeConnections: number;
backends: Array<{
id: string;
healthy: boolean;
connections: number;
totalRequests: number;
}>;
}Use Cases
1. Local Development
Run multiple instances of your app locally and distribute traffic between them.
2. E2E Testing
Test your application's behavior with multiple instances, including failover scenarios.
3. Integration Testing
Verify load distribution, session persistence, and health checking logic.
4. Performance Testing
Test how your application handles distributed load.
5. Prototype Distributed Systems
Quickly prototype and test distributed architectures locally.
Requirements
- Node.js >= 18.0.0
License
MIT
Contributing
Contributions are welcome! This package is designed to be simple yet powerful for local development and testing scenarios.
Related
- http-proxy - The underlying proxy library
- Human4AI Platform
