From bcf51dbc53ffacca27cef7af8d0fa17ec21c4a06 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 21:24:28 +0100 Subject: [PATCH 1/8] allow node clustering out of the box like other frameworks --- package.json | 3 +- src/index.ts | 4 ++- test/cluster/cluster.js | 74 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 test/cluster/cluster.js diff --git a/package.json b/package.json index 39837d5..89db65a 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "license": "MIT", "scripts": { "dev": "tsx --watch ./example/index.ts", - "test": "bun run test:node", + "test": "bun run test:node && bun run test:node-cluster", "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", + "test:node-cluster": "node ./test/cluster/cluster.js port && node ./test/cluster/cluster.js object && node ./test/cluster/cluster.js true && node ./test/cluster/cluster.js false", "build": "bun build.ts", "release": "npm run build && npm run test && npm publish --access public" }, diff --git a/src/index.ts b/src/index.ts index a69b286..e2a4d65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -107,9 +107,11 @@ export const node = () => { port: options, silent: true, websocket, - fetch: app.fetch + fetch: app.fetch, + reusePort: true } : { + reusePort: true, ...options, silent: true, websocket, diff --git a/test/cluster/cluster.js b/test/cluster/cluster.js new file mode 100644 index 0000000..d1653b8 --- /dev/null +++ b/test/cluster/cluster.js @@ -0,0 +1,74 @@ +import cluster from 'node:cluster' +import { Elysia } from 'elysia' +import { exit, pid } from 'node:process' +import { node } from '@elysiajs/node' + +const workersAmount = 5 +const port = 3000 +const arg2 = process.argv[2] +let parameter +if (arg2 === 'port') { + parameter = port +} else if (arg2 === 'object') { + parameter = { + port + } +} else if (arg2 === 'true') { + parameter = { + port, + reusePort: true + } +} else if (arg2 === 'false') { + parameter = { + port, + reusePort: false + } +} + +function shutdown(workers, code) { + workers.forEach((it) => { + it.kill() + }) + exit(0) +} + +if (cluster.isPrimary) { + let workers = [] + for (let i = 0; i < workersAmount; i++) { + workers.push(cluster.fork()) + } + // we need some delay to allow Elysia to initialize + const delayPromise = new Promise((resolve, reject) => { + setTimeout(() => { + resolve() + }, 2000) + }) + await delayPromise + //Make n API calls, we should receive n different PIDs back + // Checking if a server is run can only be done this way at the moment + // because error is deep in srvx + // Even callback in Elysia.listen will be still be run even on error + const promises = workers.map(async (it) => { + const result = await fetch(`http://localhost:${port}`) + const pid = await result.text() + return pid + }) + const result = await Promise.all(promises) + + if (arg2 === 'false') { + if (new Set(result).size != 1) { + console.error('❌ Server should return 1 pid.') + shutdown(workers, 1) + } + console.log('✅ Test succeed!') + shutdown(workers, 0) + } + if (new Set(result).size != workersAmount) { + console.error("❌ Clustering error, number of pids doesn't match.") + shutdown(workers, 1) + } + console.log('✅ Test succeed!') + shutdown(workers, 0) +} else { + new Elysia({ adapter: node() }).get(`/`, pid).listen(parameter) +} From 63427adbfa1ee54729cf7888edb7c1fd8995fda7 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 21:32:38 +0100 Subject: [PATCH 2/8] just little comment update --- test/cluster/cluster.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/cluster/cluster.js b/test/cluster/cluster.js index d1653b8..f7b2096 100644 --- a/test/cluster/cluster.js +++ b/test/cluster/cluster.js @@ -5,6 +5,8 @@ import { node } from '@elysiajs/node' const workersAmount = 5 const port = 3000 +// Use args to differentiate paramater +// So code is more compact const arg2 = process.argv[2] let parameter if (arg2 === 'port') { @@ -44,9 +46,10 @@ if (cluster.isPrimary) { }, 2000) }) await delayPromise - //Make n API calls, we should receive n different PIDs back + + // Make n API calls, we should receive n different PIDs back // Checking if a server is run can only be done this way at the moment - // because error is deep in srvx + // because error is really deep in srvx and async // Even callback in Elysia.listen will be still be run even on error const promises = workers.map(async (it) => { const result = await fetch(`http://localhost:${port}`) From 95f77fa0728033202c82ee1c1f89a3b30cb15e73 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 21:53:40 +0100 Subject: [PATCH 3/8] adjusted console log --- test/cluster/cluster.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cluster/cluster.js b/test/cluster/cluster.js index f7b2096..0bcee2c 100644 --- a/test/cluster/cluster.js +++ b/test/cluster/cluster.js @@ -63,14 +63,14 @@ if (cluster.isPrimary) { console.error('❌ Server should return 1 pid.') shutdown(workers, 1) } - console.log('✅ Test succeed!') + console.log('✅ Test exclusive mode succeed!') shutdown(workers, 0) } if (new Set(result).size != workersAmount) { console.error("❌ Clustering error, number of pids doesn't match.") shutdown(workers, 1) } - console.log('✅ Test succeed!') + console.log('✅ Test cluster mode succeed!') shutdown(workers, 0) } else { new Elysia({ adapter: node() }).get(`/`, pid).listen(parameter) From a2a470928bd9df6e72bb8abb83e3b7b5ec6a5016 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 21:56:39 +0100 Subject: [PATCH 4/8] use variable instead --- test/cluster/cluster.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/cluster/cluster.js b/test/cluster/cluster.js index 0bcee2c..e8eeb2e 100644 --- a/test/cluster/cluster.js +++ b/test/cluster/cluster.js @@ -57,16 +57,16 @@ if (cluster.isPrimary) { return pid }) const result = await Promise.all(promises) - + const pidsCount = new Set(result).size; if (arg2 === 'false') { - if (new Set(result).size != 1) { + if (pidsCount != 1) { console.error('❌ Server should return 1 pid.') shutdown(workers, 1) } console.log('✅ Test exclusive mode succeed!') shutdown(workers, 0) } - if (new Set(result).size != workersAmount) { + if (pidsCount != workersAmount) { console.error("❌ Clustering error, number of pids doesn't match.") shutdown(workers, 1) } From 416efb5376b067a0ac0c44d883b9f23b86aa0009 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 22:03:20 +0100 Subject: [PATCH 5/8] forgot to actually use code --- test/cluster/cluster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cluster/cluster.js b/test/cluster/cluster.js index e8eeb2e..12cc4bb 100644 --- a/test/cluster/cluster.js +++ b/test/cluster/cluster.js @@ -31,7 +31,7 @@ function shutdown(workers, code) { workers.forEach((it) => { it.kill() }) - exit(0) + exit(code) } if (cluster.isPrimary) { From a1eff3ab43f0415e47d9e9da49f628c16e423f32 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 22:20:00 +0100 Subject: [PATCH 6/8] use cjs instead of esm --- package.json | 2 +- test/cluster/{cluster.js => cluster.cjs} | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) rename test/cluster/{cluster.js => cluster.cjs} (89%) diff --git a/package.json b/package.json index 89db65a..52a638a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "tsx --watch ./example/index.ts", "test": "bun run test:node && bun run test:node-cluster", "test:node": "npm install --prefix ./test/node/cjs/ && npm install --prefix ./test/node/esm/ && node ./test/node/cjs/index.js && node ./test/node/esm/index.js", - "test:node-cluster": "node ./test/cluster/cluster.js port && node ./test/cluster/cluster.js object && node ./test/cluster/cluster.js true && node ./test/cluster/cluster.js false", + "test:node-cluster": "node ./test/cluster/cluster.cjs port && node ./test/cluster/cluster.cjs object && node ./test/cluster/cluster.cjs true && node ./test/cluster/cluster.cjs false", "build": "bun build.ts", "release": "npm run build && npm run test && npm publish --access public" }, diff --git a/test/cluster/cluster.js b/test/cluster/cluster.cjs similarity index 89% rename from test/cluster/cluster.js rename to test/cluster/cluster.cjs index 12cc4bb..c60e891 100644 --- a/test/cluster/cluster.js +++ b/test/cluster/cluster.cjs @@ -1,7 +1,7 @@ -import cluster from 'node:cluster' -import { Elysia } from 'elysia' -import { exit, pid } from 'node:process' -import { node } from '@elysiajs/node' +const cluster = require('node:cluster') +const { Elysia } = require('elysia') +const { exit, pid } = require('node:process') +const { node } = require('@elysiajs/node') const workersAmount = 5 const port = 3000 @@ -34,7 +34,8 @@ function shutdown(workers, code) { exit(code) } -if (cluster.isPrimary) { + +async function startPrimary() { let workers = [] for (let i = 0; i < workersAmount; i++) { workers.push(cluster.fork()) @@ -72,6 +73,10 @@ if (cluster.isPrimary) { } console.log('✅ Test cluster mode succeed!') shutdown(workers, 0) +} + +if (cluster.isPrimary) { + startPrimary() } else { new Elysia({ adapter: node() }).get(`/`, pid).listen(parameter) } From 107f5c097330fedd5a3a6c403d4a78ff1a97af3a Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 22:31:51 +0100 Subject: [PATCH 7/8] adjusted code rabbit comments --- test/cluster/cluster.cjs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/test/cluster/cluster.cjs b/test/cluster/cluster.cjs index c60e891..0686743 100644 --- a/test/cluster/cluster.cjs +++ b/test/cluster/cluster.cjs @@ -5,7 +5,7 @@ const { node } = require('@elysiajs/node') const workersAmount = 5 const port = 3000 -// Use args to differentiate paramater +// Use args to differentiate parameter // So code is more compact const arg2 = process.argv[2] let parameter @@ -27,6 +27,11 @@ if (arg2 === 'port') { } } +/** + * Method to stop workers and then exit the current program + * @param {Worker[]} workers list of workers + * @param {number} code exit code + */ function shutdown(workers, code) { workers.forEach((it) => { it.kill() @@ -34,19 +39,18 @@ function shutdown(workers, code) { exit(code) } - +/** + * Start primary node + * This will create workers and then send request to primary which will then + * spread the requests to the workers. After that it will check for the result. + */ async function startPrimary() { let workers = [] for (let i = 0; i < workersAmount; i++) { workers.push(cluster.fork()) } // we need some delay to allow Elysia to initialize - const delayPromise = new Promise((resolve, reject) => { - setTimeout(() => { - resolve() - }, 2000) - }) - await delayPromise + await new Promise((resolve) => setTimeout(resolve, 2000)) // Make n API calls, we should receive n different PIDs back // Checking if a server is run can only be done this way at the moment @@ -54,20 +58,20 @@ async function startPrimary() { // Even callback in Elysia.listen will be still be run even on error const promises = workers.map(async (it) => { const result = await fetch(`http://localhost:${port}`) - const pid = await result.text() - return pid + const workerPid = await result.text() + return workerPid }) const result = await Promise.all(promises) const pidsCount = new Set(result).size; if (arg2 === 'false') { - if (pidsCount != 1) { + if (pidsCount !== 1) { console.error('❌ Server should return 1 pid.') shutdown(workers, 1) } console.log('✅ Test exclusive mode succeed!') shutdown(workers, 0) } - if (pidsCount != workersAmount) { + if (pidsCount !== workersAmount) { console.error("❌ Clustering error, number of pids doesn't match.") shutdown(workers, 1) } From da6fe8e349038152d9e3f36353492206c36b06c5 Mon Sep 17 00:00:00 2001 From: dhani Date: Fri, 23 Jan 2026 22:41:32 +0100 Subject: [PATCH 8/8] fallback throw an error for arg2 not recognized --- test/cluster/cluster.cjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/cluster/cluster.cjs b/test/cluster/cluster.cjs index 0686743..454fd1e 100644 --- a/test/cluster/cluster.cjs +++ b/test/cluster/cluster.cjs @@ -25,6 +25,9 @@ if (arg2 === 'port') { port, reusePort: false } +} else { + console.error(`Unknown argument: ${arg2}. Expected: port, object, true, false`) + exit(1) } /**