ref – https://livecodestream.dev/post/when-you-should-and-should-not-use-nodejs-for-your-project/
A multi-threaded program has a pool of threads that run concurrently to accept requests. The number of threads used by the program is limited by the system’s RAM or other parameters. So, the number of requests it can process at a given time is limited to this number of threads.
Whereas the single-threaded Node.js program can process any number of requests at a given time that are in the event queue to be executed. Node’s event loop uses a single-threaded implementation when handling events. Each event is handled one after another in the order they arrive by a single processor thread. The requests received to a Node web server are served in the order they were received.
However, there is a downside to Node.js being single-threaded. The single-threaded implementation makes Node a bad choice for CPU-intensive programs. When a time-consuming task is running in the program it blocks the event loop from moving forward for a longer period. Unlike in a multi-threaded program, where one thread can be doing the CPU-intensive task and others can handle arriving requests,
Node.js introduced a workaround for this problem in version 10.5.0: worker threads. You can read up more on this topic to understand how to use worker threads to solve this problem for CPU-intensive programs.
You may now have a question? How can a single thread handle more events at a given time than a thread pool if events are handled one after another and not concurrently? This is where Node’s asynchronous model steps in. Even though the Node’s event loop implementation is single-threaded, it achieves a concurrent-like behavior with the use of callbacks.
Assume you want to connect to a separate API from your Node server to retrieve some data.
You send a request to this particular API from the server. You also pass a callback function with the instruction on what to do when the response from the API is received.
After executing the task that makes the request to the API, the Node program doesn’t wait for the response from the that API. Instead, after sending the request, it continues to the next step of the program. When the response from the API arrives the callback function starts running, and therefore handles the received data. In this manner, the callback function runs concurrently to the main program thread.
In contrast, in a synchronous multi-threaded program, the thread sending making the API call waits for the response to arrive to continue to the next step. This does not stall the multi-threaded program because, even though this one thread is waiting, other threads in the thread pool are ready to accept receiving requests.
The asynchronous feature is what makes the single-thread of the Node.js program quite efficient. It makes the program fast and scalable without using as many resources as a multi-threaded application.
Naturally, this makes Node a good fit for data-intensive and I/O intensive programs. Data-intensive programs are focus on retrieving and storing data, while I/O-intensive programs focus on interacting with external resources.
When to Use Node.js?
Now back to our main question. When should you use Node.js for your projects? Considering the main features of Node.js and its strengths, we can say
– data-driven
– I/O-driven
– event-driven
– non-blocking
applications benefit the best from a Node.js implementation.
Especially, web backends that follow microservices architecture, which is now growing in popularity, are well suited for a Node implementation. With the microservices architecture, sending requests from one microservice to another becomes inevitable. With Node’s asynchronous programming, waiting for responses from outside resources can be done without blocking the program thread.
Unlike a multi-threaded program, which can run out of threads to serve incoming requests because many threads are waiting for responses from external resources, this I/O-intensive architecture does not affect the performance of the Node application. However, web servers that implement computational-heavy or processes-heavy tasks are not a good fit for Node.js. The programming community is still apprehensive of using Node with a relational database given Node tools for relational databases are still not as developed as compared to other languages.
Heavy Computational Applications
If your application is likely to run tasks that involve heavy computing and number crunching, like running the Fibonacci algorithm, Node.js is not the best language to turn to. As discussed above, the heavy computation blocks the single-thread running in the application and halts the progress of the event loop until the computation is finished. And it delays serving the requests still in the queue, which may not take as much time.
If your application has a few heavy computational tasks, but in general benefits from Node’s characteristics, the best solution is to implement those heavy computational tasks as background processes in another appropriate language. Using microservices architecture and separating heavy computational tasks from Node implementation is the best solution.