Top 5 Node.js Features Every Developer Should Know

Explore Node.js Worker Threads, Cluster, http2, Streams API and REPL

Danusha Navod
Bits and Pieces

--

Whether you’re an experienced Node.js developer or just starting your journey, there’s always something waiting to be unleashed within Node.js.

In this article, I’ll explore five Node.js features that can enhance your overall experience, and those are:

  • Worker Threads
  • Cluster Process Module
  • Built-in HTTP/2 Support
  • Streams API
  • REPL

Are you excited to dive in? Let’s explore each of these features one by one.

But before we do, let’s quickly take a look at the Single-Threaded behavior of Node.js. Understanding this foundational aspect will provide valuable context for the features we’re about to delve into.

Single-Threaded Node.js: A Quick Overview

Node.js is known for its single-threaded architecture. But more accurate to call it a “single-threaded event loop”.

But why a Single-Threaded Event Loop?

Initially, Node.js was designed for I/O-bound tasks like web servers. For these, creating multiple threads adds overhead and complexity in managing thread synchronization and context switching. Instead, Node.js adopted an event-driven approach.

This behavior brings several advantages and also limitations to Node.js.

The advantages are okay. But what about limitations?

The main limitations that the Node.js Single-Threaded Event Loop brings are as follows,

  • CPU-bound tasks can block the loop: Extensive calculations can “freeze” the loop, affecting responsiveness for other requests.
  • No true parallelism: Tasks are still executed one after another, not simultaneously.

To address these limitations, Node.js introduced Worker Threads and the Cluster Module in various Node.js versions.

These two features can truly impact your software development journey. So, let’s delve into Worker Threads and the Cluster Module in the upcoming sections to understand their incredible usefulness.

Afterwards, we’ll explore three more Node.js features that can come to your rescue in various situations. Stay tuned!

1. Worker Threads

(https://nodesource.com/blog/worker-threads-nodejs/)

While the single-threaded event loop excels at handling I/O-bound tasks, Node.js’s worker_threads module empowers you to break free from its limitations when dealing with CPU-bound operations.

Imagine having multiple chefs working independently in the kitchen, simultaneously preparing different dishes (tasks) — that’s the essence of worker threads!

What’s happening under the hood?

Node.js, by default, has a single-threaded event loop that excels at handling I/O-bound tasks. But for CPU-bound tasks, it can become a bottleneck.

Think of worker threads as separate JavaScript execution contexts within the same Node.js process.

Instead of the main thread handling everything, it can delegate CPU-intensive tasks to these worker threads. This allows the main thread to remain responsive and handle other requests while the worker threads crunch away on the complex calculations.

Essentially, worker threads allow you to:

  • Offload CPU-bound tasks: Free up the main thread for other work.
  • Achieve parallelism: Execute tasks concurrently for faster performance.
  • Share data efficiently: Avoid the overhead of copying data between processes.

Getting started with worker threads

The worker_threads module provides a simple API for creating and communicating with worker threads:

const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js', { data: { someData: 'to process' } });

worker.on('message', (message) => {
console.log(Received message from worker: ${message} );
});

worker.postMessage({ anotherData: 'to send' });

Remember, worker threads share memory. So data structures like ArrayBuffer or SharedArrayBuffer are recommended for large data exchanges to avoid unnecessary copying.

Also remember:

  • Creating and managing worker threads has some overhead, so consider its benefit vs. cost for your specific use case.
  • Thread safety is crucial! Use synchronization mechanisms to ensure data integrity.
  • Worker threads add complexity, so use them judiciously for tasks that truly benefit from parallelism.

2. Cluster Process Module

(https://cheatcode.co/tutorials/how-to-add-cluster-support-to-node-js)

While worker threads work great for parallelism, the cluster module empowers you to go even further in your multi-core system.

Imagine having multiple kitchens (Node.js processes) running independently, each handling requests simultaneously — that’s the power of clustering!

What’s happening under the hood?

The Cluster Module creates multiple separate Node.js processes, each with its own event loop and memory space.

These processes run independently on different cores, utilizing multiple cores for improved performance (Horizontal Scaling).

This operates by creating a master process and several worker processes. The master process manages the distribution of incoming connections among the worker processes. If a worker process fails, the master process can respawn a new one, ensuring robustness in the face of failures.

But why embrace the Cluster?

  • Boost Performance: Handle higher traffic volumes and improve response times, especially for I/O-bound tasks.
  • Maximize Resource Utilization: Take advantage of all available cores in your server, significantly increasing processing power.
  • Enhanced Fault Tolerance: If one worker crashes, others keep the application running, ensuring reliability and uptime.

Getting Started with the Cluster

The cluster module provides a straightforward API for setting up and managing worker processes:

const cluster = require('cluster');

if (cluster.isMaster) {
// Master process
const numWorkers = require('os').cpus().length;

for (let i = 0; i < numWorkers; i++) {
cluster.fork();
}

cluster.on('exit', (worker, code, signal) => {
console.log(worker ${worker.process.pid} died );
});
} else {
// Worker process
// Your application logic here
app.listen(3000);
}

Remember:

  • Worker processes share memory and resources, so consider data synchronization carefully.
  • The cluster module adds complexity to your application architecture, so evaluate its benefit vs. complexity for your specific needs.

When to Consider the Cluster:

  • High-Traffic Websites: When your single-threaded event loop reaches its limits, scaling horizontally with cluster helps manage large user bases efficiently.
  • Long-Running Tasks: If some requests involve lengthy operations (like image processing or data encryption), distributing them across worker processes improves responsiveness for other requests.
  • Fault Tolerance is Critical: For mission-critical applications, the cluster module’s resilience to individual process failures offers valuable protection.

3. Built-in HTTP/2 Support

(https://github.com/nodejs/http2)

While worker threads and the cluster module address different concerns, Node.js’s http2 module tackles performance directly by offering built-in support for the efficient HTTP/2 protocol.

What is this HTTP/2?

HTTP/2, the successor to HTTP/1.1, brings several performance enhancements:

  • Multiplexing: Enables simultaneous sending and receiving of multiple requests and responses on a single connection, eliminating the head-of-line blocking issue plaguing HTTP/1.1.
  • Header compression: Shrinks header size by compressing them, dramatically reducing data transmission overhead.
  • Server push: Allows servers to proactively send resources to clients before they request them, potentially accelerating page load times.

How does Node.js provide support for HTTP/2?

Node.js provides a robust http2 module for working with HTTP/2. Here are some of the features it offers:

  • Creating HTTP/2 servers: Use familiar Node.js server patterns with additional options for managing streams and server push functionality.
  • Handling HTTP/2 clients: Access client-side capabilities to connect to and interact with HTTP/2 servers.
  • Extensive API: Explore various methods and events to manage connections, streams, push mechanisms, and error handling.

Getting started with http2

Node.js documentation offers detailed guides and examples for using the http2 module. However, simply providing a link isn’t enough. Let’s jump into some practical examples to demonstrate its usage.

1. Creating a basic HTTP/2 Server:

const http2 = require('http2');

const server = http2.createServer();

server.on('stream', (stream, headers) => {
stream.respond({
'status': 200,
'content-type': 'text/plain',
});
stream.end('Hello from your HTTP/2 server!');
});

server.listen(3000, () => {
console.log('Server listening on port 3000');
});

This code creates a simple server that sends a “Hello” message to any client connecting through HTTP/2.

2. Handling Client Requests:

const http2 = require('http2');

const server = http2.createServer();

server.on('stream', (stream, headers) => {
const path = headers[':path'];

if (path === '/') {
stream.respond({
'status': 200,
'content-type': 'text/plain',
});
stream.end('Hello from HTTP/2 server!');
} else {
stream.respond({
'status': 404,
'content-type': 'text/plain',
});
stream.end('Not found');
}
});

server.listen(3000, () => {
console.log('Server listening on port 3000');
});

This code extends the previous example to handle different request paths (/) and send appropriate responses.

4. Streams API

(https://www.scaler.com/topics/nodejs/streams-in-nodejs/)

Node.js’s Streams API provides a powerful foundation for efficient data handling in your applications. Understanding streams helps you build scalable and performant systems.

What are Streams?

Imagine data flowing like a stream of water — that’s essentially the concept.

Streams represent continuous sequences of data chunks delivered over time. Node.js provides various stream types, each catering to different use cases:

  • Readable Streams: Emit data chunks for consumption, ideal for reading files, network connections, or user input.
  • Writable Streams: Allow writing data chunks, perfect for writing to files, network connections, or databases.
  • Duplex Streams: Combine reading and writing capabilities, useful for bidirectional communication like sockets or pipes.
  • Transform Streams: Modify data as it flows through, enabling encryption, compression, or data manipulation.

Why should you use Streams?

Streams shine in scenarios where large datasets or continuous data flows are involved. They offer several advantages:

  • Memory efficiency: They handle data in chunks, avoiding loading the entire dataset into memory at once.
  • Non-blocking nature: They don’t block the main thread, allowing your application to remain responsive while processing data.
  • Flexibility: Different stream types cater to various data handling needs.

Getting Started with Streams

Exploring the built-in fs module provides a practical introduction to streams. Here's an example reading a file chunk by chunk:

const fs = require('fs');

const readableStream = fs.createReadStream('large_file.txt');

readableStream.on('data', (chunk) => {
console.log('Received data chunk:', chunk.toString());
});

readableStream.on('end', () => {
console.log('Finished reading file');
});

This code reads the large_file.txt file in chunks and logs them to the console. Explore the Node.js documentation for more types and their usage.

5. REPL (Read-Eval-Print Loop)

(https://www.scaler.com/topics/nodejs/node-js-repl/)

While worker threads and the cluster module contribute to improved performance and scalability, the dynamic duo of HTTP/2 and streams extends their abilities, offering versatile benefits across multiple domains. On a different front, the REPL (Read-Eval-Print Loop) introduces a distinct kind of power — interactivity and exploration.

Imagine a sandbox environment where you can experiment with code snippets, test ideas, and get immediate feedback — that’s the essence of REPL.

Think of it as a conversational coding experience. You type in code expressions, and the REPL evaluates them and displays the results, allowing you to iterate and learn quickly. This makes REPL invaluable for:

  • Learning and Experimentation: Try out new JavaScript features, explore libraries, and test hypotheses in a safe, isolated environment.
  • Debugging and Troubleshooting: Isolate and fix issues in your code line by line, inspecting variables and values at each step.
  • Interactive Development: Prototype ideas quickly, get immediate feedback, and refine your code iteratively.

Accessing the REPL:

Open your terminal and simply type node. Voilà! You’re now in the REPL, ready to play. Type any JavaScript variable assignment, a function call, or even a complex calculation.

Welcome to Node.js v20.11.0.
Type ".help" for more information.
> Math.random()
0.6148448277159013

When compared to all the robust features outlined earlier, the REPL may seem deceptively simple. However, its true value becomes evident only through hands-on experience. As a Node.js developer, embracing and integrating the REPL into your workflow is not just beneficial but essential.

Concluding Thoughts

Among the powerful arsenal of tools that Node.js provides, worker threads tackle CPU-bound tasks, the cluster module enables horizontal scaling, and http2 gives the power of HTTP/2 network protocol. Streams provide efficient data handling, and the REPL empowers interactive exploration and learning.

By mastering these features, you’ll unlock the full potential of Node.js and build performant, scalable, and enjoyable development experiences.

--

--