When single threaded Node.js becomes a trouble

As you know (as it is posted a dozen times a day), Javascript is a single-threaded language.

Thus, we will have a single thread serving all of our requests in our Node.js server unless we specifically play around this.

This can cause some troubles that you wouldn’t normally see when you write a web server in any multi-threaded language such as Go, Rust, Java, C++, or Python.


The problem

Here is a scenario where this can get your server into trouble since one of your requests is CPU-intensive and the single thread that is supposed to serve for all requests is busy with calculating fibonacci.

/hello : directly returns "Hello"

/fibonacci/:n : calculates nth fibonacci number

const express = require("express");
const app = express();
const port = 3000;

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

app.get("/hello", (req, res) => {
  res.send("Hello");
});

app.get("/fibonacci/:n", (req, res) => {
  const n = parseInt(req.params.n);
  const result = fibonacci(n);
  res.send(`Fibonacci(${n}) = ${result}`);
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

If you want to try, just run this simple express server on your end and;

1- Send a request to /fibonacci/:n with a large number like 100

2- Try to send a request to /hello, and you won’t be able to get a response until the first request is completed.

If we tried the same thing in a language like Go, we wouldn’t have to wait for the Fibonacci to complete since Go creates a new light thread (goroutine) for each request in all web server implementations.


The fix

Node.js comes with a worker threads api, which allows you to run jobs in parallel threads. It can even utilize the other cores of your CPU and handle them there without blocking the main thread.

Here is how we would utilize it to run fibonacci calculation in parallel so that we don’t block the main thread and main thread can serve for other requests.

index.js

const express = require("express");
const { Worker } = require("worker_threads");
const path = require("path");

const app = express();
const port = 3000;

function runFibonacciWorker(n) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(path.join(__dirname, "worker.js"), {
      workerData: { n },
    });
    worker.on("message", resolve);
    worker.on("error", reject);
    worker.on("exit", (code) => {
      if (code !== 0)
        reject(new Error(`Worker stopped with exit code ${code}`));
    });
  });
}

app.get("/hello", (req, res) => {
  res.send("Hello");
});

app.get("/fibonacci/:n", async (req, res) => {
  const n = parseInt(req.params.n);
  try {
    const result = await runFibonacciWorker(n);
    res.send(`Fibonacci(${n}) = ${result}`);
  } catch (err) {
    res.status(500).send(err.message);
  }
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

worker.js

const { parentPort, workerData } = require("worker_threads");

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

parentPort.postMessage(fibonacci(workerData.n));

Now we are able to send a request to /fibonacci/:n with a blocking number like 100, but we are still able to send requests to /hello and get instant responses because the Fibonacci calculation is working in a separate thread.

Dancing Cat