smart-stick-loadbalancer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Smart Stick Load Balancer
2
+
3
+ A lightweight **sticky-session load balancer** for Node.js. It distributes requests to multiple backends, supports sticky sessions via cookies, performs health checks, and can optionally send email alerts.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - Sticky sessions via cookies
10
+ - Health checks with automatic failover
11
+ - Optional email alerts
12
+ - WebSocket and HTTP support
13
+ - Minimal setup
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install smart-stick-loadbalancer
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Example Configuration (`config.json`)
26
+
27
+ ```json
28
+ {
29
+ "port": 3001,
30
+ "backends": [
31
+ { "id": 0, "url": "http://localhost:5000", "owner": "example@example.com", "weight": 1 },
32
+ { "id": 1, "url": "http://localhost:5001", "owner": "example@example.com", "weight": 1 }
33
+ ],
34
+ "health": { "interval": 10000, "timeout": 2000 },
35
+ "email": {
36
+ "service": "gmail",
37
+ "auth": { "user": "<your-email@gmail.com>", "pass": "<your-app-password>" }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Usage
45
+
46
+ ```js
47
+ const { createStickyProxy } = require('smart-stick-loadbalancer');
48
+ const config = require('./config.json');
49
+
50
+ const lb = createStickyProxy(config);
51
+ lb.start(); // starts the load balancer
52
+ ```
53
+
54
+ - Requests to `http://localhost:3001` will be forwarded to the backends.
55
+ - Sticky sessions are automatically managed using cookies.
56
+ - Health checks run periodically and remove unhealthy backends from rotation.
57
+
58
+ ---
59
+
60
+ ## Health Endpoint
61
+
62
+ ```http
63
+ GET /_lb/health
64
+ ```
65
+
66
+ Response example:
67
+
68
+ ```json
69
+ {
70
+ "timestamp": "2025-12-28T12:00:00.000Z",
71
+ "total": 2,
72
+ "healthy": 2,
73
+ "unhealthy": 0,
74
+ "backends": [
75
+ { "id": 0, "url": "http://localhost:5000", "healthy": true, "requests": 5 },
76
+ { "id": 1, "url": "http://localhost:5001", "healthy": true, "requests": 3 }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Email Alerts
84
+
85
+ - Alerts are sent when a backend goes down or comes back up.
86
+ - Requires valid Gmail credentials (or any supported email service).
87
+
88
+ ---
89
+
90
+ ## License
91
+
92
+ MIT
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "smart-stick-loadbalancer",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight sticky-session load balancer for Node.js with health checks and optional email alerts.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "smart-stick-lb": "src/index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/index.js"
11
+ },
12
+ "keywords": [
13
+ "load-balancer",
14
+ "sticky-session",
15
+ "proxy",
16
+ "Node.js",
17
+ "http-proxy",
18
+ "health-check"
19
+ ],
20
+ "author": "SWAYAM GUPTA",
21
+ "license": "MIT",
22
+ "dependencies": {
23
+ "axios": "^1.10.0",
24
+ "cookie-parser": "^1.4.7",
25
+ "express": "^5.1.0",
26
+ "http-proxy-middleware": "^3.0.5",
27
+ "nodemailer": "^7.0.5",
28
+ "socket.io": "^4.8.3"
29
+ }
30
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "port": 3001,
3
+ "backends": [
4
+ {
5
+ "id": 0,
6
+ "url":"<link to reviewer instance>",
7
+ "owner": "<reviewer email>",
8
+ "weight": 1
9
+ },
10
+ {
11
+ "id": 1,
12
+ "url": "<link to reviewer instance>",
13
+ "owner": "<reviewer email>",
14
+ "weight": 1
15
+ }
16
+ ],
17
+ "health": {
18
+ "interval": 10000,
19
+ "timeout": 2000
20
+ },
21
+ "email": {
22
+ "service": "gmail",
23
+ "auth": {
24
+ "user": "<sendermail>@gmail.com",
25
+ "pass": "<app password>"
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,32 @@
1
+ const axios = require("axios");
2
+
3
+ async function checkHealth(server, timeout = 2000) {
4
+ try {
5
+ await axios.get(server.url, { timeout });
6
+ return true;
7
+ } catch {
8
+ return false;
9
+ }
10
+ }
11
+
12
+ function startHealthChecker(servers, config, onDown, onUp) {
13
+ const lastStatus = new Map();
14
+ const interval = config?.interval || 10000;
15
+ const timeout = config?.timeout || 2000;
16
+
17
+ setInterval(async () => {
18
+ for (const server of servers) {
19
+ const isUp = await checkHealth(server, timeout);
20
+ const wasUp = lastStatus.get(server.url) ?? true;
21
+ server.healthy = isUp;
22
+ server.lastChecked = new Date().toISOString();
23
+
24
+ if (!isUp && wasUp && onDown) onDown(server);
25
+ if (isUp && !wasUp && onUp) onUp(server);
26
+
27
+ lastStatus.set(server.url, isUp);
28
+ }
29
+ }, interval);
30
+ }
31
+
32
+ module.exports = { checkHealth, startHealthChecker };
package/src/index.js ADDED
@@ -0,0 +1,108 @@
1
+ const express = require("express");
2
+ const http = require("http");
3
+ const { createProxyMiddleware } = require("http-proxy-middleware");
4
+ const cookieParser = require("cookie-parser");
5
+ const socketIo = require("socket.io");
6
+ const { startHealthChecker } = require("./healthChecker");
7
+ const { sendDownAlert, sendUpAlert } = require("./notifier");
8
+
9
+ function createStickyProxy(options = {}) {
10
+ if (!options.port || !options.backends) {
11
+ throw new Error("Port and backends must be provided");
12
+ }
13
+
14
+ const app = express();
15
+ const server = http.createServer(app);
16
+ const io = socketIo(server);
17
+
18
+ let current = 0;
19
+ const servers = options.backends.map((b) => ({
20
+ ...b,
21
+ healthy: true,
22
+ requests: 0,
23
+ lastChecked: null,
24
+ }));
25
+
26
+ // Middleware
27
+ app.use(cookieParser());
28
+
29
+ // Health endpoint
30
+ app.get("/_lb/health", (req, res) => {
31
+ res.json({
32
+ timestamp: new Date().toISOString(),
33
+ total: servers.length,
34
+ healthy: servers.filter((s) => s.healthy).length,
35
+ unhealthy: servers.filter((s) => !s.healthy).length,
36
+ backends: servers.map((s) => ({
37
+ id: s.id,
38
+ url: s.url,
39
+ healthy: s.healthy,
40
+ requests: s.requests,
41
+ lastChecked: s.lastChecked,
42
+ })),
43
+ });
44
+ });
45
+
46
+ // Sticky selection logic
47
+ function getHealthyBackend(req, res) {
48
+ const healthyBackends = servers.filter((s) => s.healthy);
49
+ if (!healthyBackends.length) return null;
50
+
51
+ let backendId = parseInt(req.cookies["X-Backend-ID"]);
52
+ if (!isNaN(backendId) && servers[backendId]?.healthy) {
53
+ return servers[backendId];
54
+ }
55
+
56
+ const selected = healthyBackends[current % healthyBackends.length];
57
+ current++;
58
+ res.cookie("X-Backend-ID", selected.id, { httpOnly: true });
59
+ return selected;
60
+ }
61
+
62
+ // Proxy instances
63
+ const proxies = new Map();
64
+ servers.forEach((backend) => {
65
+ proxies.set(
66
+ backend.id,
67
+ createProxyMiddleware({
68
+ target: backend.url,
69
+ changeOrigin: true,
70
+ ws: true,
71
+ onProxyRes(proxyRes, req, res) {
72
+ res.setHeader("x-backend", backend.url);
73
+ },
74
+ })
75
+ );
76
+ });
77
+
78
+ // Proxy handler
79
+ app.use((req, res, next) => {
80
+ const backend = getHealthyBackend(req, res);
81
+ if (!backend) return res.status(503).send("All servers down.");
82
+
83
+ backend.requests++;
84
+ io.emit("request", { ip: req.ip, to: backend.url });
85
+ io.emit("update", servers);
86
+
87
+ const proxy = proxies.get(backend.id);
88
+ proxy(req, res, next);
89
+ });
90
+
91
+ // Health checks with optional email alerts
92
+ startHealthChecker(
93
+ servers,
94
+ options.health || { interval: 10000, timeout: 2000 },
95
+ options.email ? (server) => { sendDownAlert(server, options.email); io.emit("update", servers); } : null,
96
+ options.email ? (server) => { sendUpAlert(server, options.email); io.emit("update", servers); } : null
97
+ );
98
+
99
+ function start() {
100
+ server.listen(options.port, () =>
101
+ console.log(`Sticky Proxy running on port ${options.port}`)
102
+ );
103
+ }
104
+
105
+ return { app, server, io, start, servers };
106
+ }
107
+
108
+ module.exports = { createStickyProxy };
@@ -0,0 +1,33 @@
1
+ const nodemailer = require("nodemailer");
2
+
3
+ function createTransport(emailConfig) {
4
+ if (!emailConfig) return null;
5
+ return nodemailer.createTransport({
6
+ service: emailConfig.service,
7
+ auth: emailConfig.auth,
8
+ });
9
+ }
10
+
11
+ function sendDownAlert(server, emailConfig) {
12
+ const transporter = createTransport(emailConfig);
13
+ if (!transporter) return;
14
+ transporter.sendMail({
15
+ from: emailConfig.auth.user,
16
+ to: server.owner,
17
+ subject: "⚠️ Backend Down",
18
+ text: `Your backend at ${server.url} is down.`,
19
+ });
20
+ }
21
+
22
+ function sendUpAlert(server, emailConfig) {
23
+ const transporter = createTransport(emailConfig);
24
+ if (!transporter) return;
25
+ transporter.sendMail({
26
+ from: emailConfig.auth.user,
27
+ to: server.owner,
28
+ subject: "✅ Backend Recovered",
29
+ text: `Your backend at ${server.url} is back online.`,
30
+ });
31
+ }
32
+
33
+ module.exports = { sendDownAlert, sendUpAlert };