Component overview
| Component | Responsibility | Location |
|---|---|---|
LoadBalancer | Orchestrates backend selection using a pluggable strategy | src/balancer/loadBalancer.ts |
BackendPool | Manages backend server registry and health state | src/balancer/pool.ts |
RoundRobin | Implements the round-robin routing algorithm | src/balancer/roundRobin.ts |
HealthChecker | Periodically verifies backend availability | src/healthchecker/healthChecker.ts |
ProxyHandler | Forwards requests and handles proxy errors | src/proxy/proxyHandler.ts |
Logger | Structured, categorized log output | src/utils/logger.ts |
LoadBalancer
Purpose: Orchestrates backend selection by combining theBackendPool and a routing strategy.
Interface
Responsibilities
- Queries the
BackendPoolfor healthy backends - Delegates the selection logic to the configured strategy
- Throws an error if no backends are available (handled by
ProxyHandler)
Interactions
How LoadBalancer interacts with other components
How LoadBalancer interacts with other components
- BackendPool: Calls
getHealthyBackends()to retrieve only backends marked as healthy - RoundRobin (or any strategy): Calls
pick()to select one backend from the healthy list - ProxyHandler: Used by
ProxyHandlerto determine which backend should receive the request
The
LoadBalancer class is agnostic to the specific routing algorithm. You can inject any strategy that implements a pick(backends: Backend[]): Backend method.BackendPool
Purpose: Central registry for all backend servers and their health status.Interface
Backend data structure
Responsibilities
- Stores all backend servers with their URLs and health status
- Provides filtered views of backends (all vs. healthy only)
- Allows health status updates via
markHealthy()andmarkUnhealthy()
Interactions
How BackendPool interacts with other components
How BackendPool interacts with other components
- LoadBalancer: Calls
getHealthyBackends()when selecting a backend for a request - HealthChecker: Calls
getAllBackends()to get the full list, then updates health status - ProxyHandler: Calls
markUnhealthy()when a proxy request fails
All backends start as healthy by default. The
HealthChecker performs an initial check immediately on startup to verify actual health status.RoundRobin
Purpose: Implements the round-robin load balancing strategy.Interface
Responsibilities
- Maintains an internal index counter
- Distributes requests evenly across all healthy backends
- Uses modulo arithmetic to cycle through backends infinitely
Algorithm details
- Initialization: Starts with
index = 0 - Selection: Returns
backends[index % backends.length] - Increment: Increments
indexafter each selection - Wraparound: When
indexexceedsbackends.length, modulo wraps it back to 0
Interactions
How RoundRobin interacts with other components
How RoundRobin interacts with other components
- LoadBalancer: Called by
LoadBalancer.pickBackend()to select the next backend - Receives a filtered list of only healthy backends from
LoadBalancer
The strategy pattern used here makes it easy to implement alternative algorithms like Least Connections, Weighted Round Robin, or IP Hash without modifying any other components.
HealthChecker
Purpose: Continuously monitors backend availability with parallel health checks.Interface
Responsibilities
- Runs health checks at a configurable interval (default: 5 seconds)
- Uses
AbortControllerto implement per-request timeouts (3 seconds) - Updates the
BackendPoolbased on health check results - Logs all health status changes
Health check criteria
- Healthy
- Unhealthy
A backend is marked healthy if:
- The HTTP request completes within 3 seconds
- The response status is
2xx(checked viares.ok)
Interactions
How HealthChecker interacts with other components
How HealthChecker interacts with other components
- BackendPool: Calls
getAllBackends()to get the full list, then callsmarkHealthy()ormarkUnhealthy() - Logger: Reports health status changes for observability
- Runs independently in the background, not triggered by requests
Health checks run in parallel using
Promise.all(). This prevents a single slow backend from blocking health evaluation of other backends.ProxyHandler
Purpose: Express middleware that forwards requests to selected backends and handles failures.Interface
Responsibilities
- Asks
LoadBalancerto pick a healthy backend - Measures request/response timing
- Forwards the request using
express-http-proxy - Handles proxy errors by marking backends unhealthy
- Returns appropriate HTTP status codes (
502for backend failure,503for no backends)
Error handling strategy
Error scenarios and responses
Error scenarios and responses
No healthy backends:
- Status:
503 Service Unavailable - Logged as:
ERROR: No healthy backends available
- Status:
502 Bad Gateway - Backend marked unhealthy immediately
- Logged as:
ERROR: Backend failed after Xms (http://backend:port)
Interactions
How ProxyHandler interacts with other components
How ProxyHandler interacts with other components
- LoadBalancer: Calls
pickBackend()to get the target backend - BackendPool: Calls
markUnhealthy()when a proxy request fails - Logger: Reports requests, responses, and errors
- Express: Registered as middleware via
app.use("/", ProxyHandler(...))
Logger
Purpose: Provides structured, categorized logging for observability.Interface
Log categories
- REQUEST
- RESPONSE
- HEALTH
- ERROR
- INFO
Logged when a request is sent to a backend:
Interactions
How Logger interacts with other components
How Logger interacts with other components
Used by all components for structured output:
- ProxyHandler: Logs requests, responses, and errors
- HealthChecker: Logs health status changes
- index.ts: Logs startup information
The structured log format makes it easy to parse logs programmatically for monitoring, alerting, and analytics.