npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

loadbalancing

v0.1.0

Published

Configurable local load balancer for development, testing, and E2E verification of distributed systems

Downloads

7

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 loadbalancing

Quick 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 backends

Sticky 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 backend

Load 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.ts

API Reference

createLoadBalancer(config)

Creates a new load balancer instance.

Parameters:

  • config: LoadBalancerConfig - Configuration object
    • backends: Backend[] - Array of backend servers (required)
    • strategy?: BalancingStrategy - Load balancing strategy (default: 'round-robin')
    • healthCheck?: HealthCheckConfig - Health check configuration
    • stickySession?: 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 balancer
  • stop(): Promise<void> - Stop the load balancer
  • addBackend(backend: Backend): void - Add a backend dynamically
  • removeBackend(backendId: string): void - Remove a backend
  • getStats(): LoadBalancerStats - Get statistics
  • getBackends(): BackendWithHealth[] - Get all backends with health status
  • isRunning(): boolean - Check if running

Events:

  • start - Load balancer started
  • stop - Load balancer stopped
  • request - HTTP request received
  • websocket - WebSocket connection
  • health-change - Backend health changed
  • no-backend - No healthy backend available
  • proxy-error - Proxy error occurred
  • backend-added - Backend added
  • backend-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