DEV Community

Sai Swaroop Bijinapalli
Sai Swaroop Bijinapalli

Posted on

Node.js Fundamentals: Runtime, Architecture, Core Modules, Streams, Buffers & Non-Blocking I/O

Sure. Since you want to create a blog on this topic, I’d structure it as a beginner-friendly technical blog that explains what Node.js is, how its architecture works, and why non-blocking I/O is important.

A good title would be:

Node.js Fundamentals: Runtime, Architecture, Core Modules, Streams, Buffers & Non-Blocking I/O

Node.js Fundamentals: Runtime, Architecture, Core Modules, Streams, Buffers & Non-Blocking I/O

Node.js is one of the most popular technologies for building backend applications using JavaScript. If you are coming from frontend JavaScript, Node.js allows you to use the same language on the server side.

But Node.js is more than simply "JavaScript outside the browser." It has its own runtime, event loop, core modules, streams, buffers, and a non-blocking I/O architecture.

In this blog, we will understand these concepts step by step.


1. What is Node.js?

Node.js is a JavaScript runtime environment that allows us to execute JavaScript outside the browser.

Normally, JavaScript runs inside a browser:

Browser
   ↓
JavaScript
   ↓
JavaScript Engine
Enter fullscreen mode Exit fullscreen mode

With Node.js:

JavaScript
    ↓
Node.js Runtime
    ↓
V8 Engine
    ↓
Operating System
Enter fullscreen mode Exit fullscreen mode

Node.js uses Google's V8 JavaScript engine to execute JavaScript.

However, Node.js also provides additional capabilities that browsers normally do not provide, such as:

  • Reading and writing files
  • Creating HTTP servers
  • Working with the operating system
  • Network communication
  • Streams
  • Buffers
  • Process management

This makes Node.js useful for backend development.


2. Node.js Runtime

A runtime environment provides everything required to execute a programming language.

The Node.js runtime can be simplified into a few major components:

                 Node.js
                    │
        ┌───────────┴───────────┐
        ↓                       ↓
       V8                     libuv
        │                       │
  Executes JS          Handles async I/O
                                │
                         Event Loop
                                │
                         Operating System
Enter fullscreen mode Exit fullscreen mode

V8

V8 is the JavaScript engine used by Node.js.

Its main responsibility is executing JavaScript code.

For example:

const name = "Node.js";

console.log(name);
Enter fullscreen mode Exit fullscreen mode

V8 is responsible for executing this JavaScript.

libuv

libuv is an important part of Node.js's asynchronous architecture.

It helps Node.js handle:

  • Event loop
  • Asynchronous I/O
  • Networking
  • Timers
  • Thread pool operations

This is one of the reasons Node.js can handle many I/O operations efficiently.


3. Node.js Architecture

A simplified Node.js architecture looks like this:

              JavaScript Code
                     ↓
                Node.js APIs
                     ↓
                  libuv
                     ↓
                Event Loop
                     ↓
        ┌────────────┴────────────┐
        ↓                         ↓
   Operating System          Thread Pool
Enter fullscreen mode Exit fullscreen mode

For example, when Node.js needs to read a file, your JavaScript code can start the operation without necessarily waiting for the entire file operation to finish.

While the operation is happening, Node.js can continue executing other JavaScript.

When the operation is complete, the appropriate callback or promise continuation can be processed.

This behavior is known as non-blocking I/O.


4. What is the Event Loop?

The event loop is one of the most important concepts in Node.js.

Node.js uses the event loop to coordinate asynchronous operations and execute callbacks when their work is ready.

Consider this example:

console.log("Start");

setTimeout(() => {
    console.log("Timer finished");
}, 1000);

console.log("End");
Enter fullscreen mode Exit fullscreen mode

The output is:

Start
End
Timer finished
Enter fullscreen mode Exit fullscreen mode

Why does "End" appear before "Timer finished"?

Because setTimeout() schedules the callback to run later.

The JavaScript execution can continue instead of waiting for the timer.

Conceptually:

console.log("Start")
        ↓
Register timer
        ↓
console.log("End")
        ↓
Timer becomes ready
        ↓
Callback executes
Enter fullscreen mode Exit fullscreen mode

The event loop continuously coordinates this kind of asynchronous work.


5. Event Loop Phases

Node.js's event loop contains several phases.

A simplified view is:

┌─────────────────────┐
│       Timers        │
├─────────────────────┤
│   Pending Callbacks │
├─────────────────────┤
│    Idle / Prepare   │
├─────────────────────┤
│        Poll         │
├─────────────────────┤
│        Check        │
├─────────────────────┤
│    Close Callbacks  │
└─────────────────────┘
          ↺
Enter fullscreen mode Exit fullscreen mode

Timers

This phase handles callbacks associated with timers such as:

setTimeout()
setInterval()
Enter fullscreen mode Exit fullscreen mode

Pending Callbacks

Some callbacks from certain system operations can be processed here.

Poll

The poll phase is responsible for handling many I/O-related callbacks and determining whether there is additional I/O work to process.

Check

The check phase is where callbacks registered using setImmediate() are executed.

setImmediate(() => {
    console.log("Immediate");
});
Enter fullscreen mode Exit fullscreen mode

Close Callbacks

This phase handles certain close events, such as socket close callbacks.

The exact event-loop behavior can become quite detailed, but the important beginner concept is:

The event loop coordinates asynchronous operations without requiring JavaScript execution to stop and wait for every I/O operation.


6. Node.js Core Modules

Node.js provides many built-in modules.

These modules can be used without installing external packages.

Some important modules are:

fs
path
os
events
stream
buffer
http
crypto
Enter fullscreen mode Exit fullscreen mode

Let's look at some of the most commonly used ones.


7. The fs Module

fs stands for File System.

It allows Node.js applications to work with files and directories.

const fs = require("fs");
Enter fullscreen mode Exit fullscreen mode

For example, we can read a file:

fs.readFile("data.txt", "utf8", (err, data) => {
    if (err) {
        console.log(err);
        return;
    }

    console.log(data);
});
Enter fullscreen mode Exit fullscreen mode

We can also write a file:

fs.writeFile("data.txt", "Hello Node.js", (err) => {
    if (err) {
        console.log(err);
    }
});
Enter fullscreen mode Exit fullscreen mode

Common fs operations include:

readFile()
writeFile()
appendFile()
mkdir()
unlink()
Enter fullscreen mode Exit fullscreen mode

The fs module is especially important when building applications that interact with files.


8. The path Module

The path module helps us work with file and directory paths.

const path = require("path");
Enter fullscreen mode Exit fullscreen mode

For example:

const filePath = path.join("users", "data", "file.txt");

console.log(filePath);
Enter fullscreen mode Exit fullscreen mode

path.join() creates a path using the correct path separator for the operating system.

Some useful methods are:

path.join()
path.resolve()
path.basename()
path.dirname()
path.extname()
Enter fullscreen mode Exit fullscreen mode

For example:

const file = "/home/user/app.js";

console.log(path.basename(file));
Enter fullscreen mode Exit fullscreen mode

Output:

app.js
Enter fullscreen mode Exit fullscreen mode

9. The os Module

The os module provides information about the operating system.

const os = require("os");
Enter fullscreen mode Exit fullscreen mode

For example:

console.log(os.platform());
console.log(os.arch());
console.log(os.cpus());
console.log(os.totalmem());
console.log(os.freemem());
Enter fullscreen mode Exit fullscreen mode

This can be useful when an application needs information about the environment in which Node.js is running.


10. The events Module

Node.js follows an event-driven architecture.

The events module provides EventEmitter, which allows us to create and respond to custom events.

const EventEmitter = require("events");

const emitter = new EventEmitter();
Enter fullscreen mode Exit fullscreen mode

We can listen for an event:

emitter.on("login", () => {
    console.log("User logged in");
});
Enter fullscreen mode Exit fullscreen mode

Then emit the event:

emitter.emit("login");
Enter fullscreen mode Exit fullscreen mode

Output:

User logged in
Enter fullscreen mode Exit fullscreen mode

The basic idea is:

.on()
  ↓
Listen for an event

.emit()
  ↓
Trigger an event
Enter fullscreen mode Exit fullscreen mode

This event-driven approach is used throughout Node.js.


11. What Are Streams?

Imagine we have a 5 GB file.

One approach would be:

5 GB file
   ↓
Load everything into memory
   ↓
Process it
Enter fullscreen mode Exit fullscreen mode

This can consume a large amount of memory.

Streams allow us to process data in smaller pieces called chunks.

Large File
    ↓
 Chunk
    ↓
Process
    ↓
 Chunk
    ↓
Process
    ↓
 Chunk
    ↓
Process
Enter fullscreen mode Exit fullscreen mode

This makes streams especially useful for large files, network communication, and other continuous data.


12. Types of Streams

Node.js has four important stream types:

Readable
Writable
Duplex
Transform
Enter fullscreen mode Exit fullscreen mode

Readable Stream

A readable stream is used to read data.

For example:

const fs = require("fs");

const stream = fs.createReadStream("large.txt");
Enter fullscreen mode Exit fullscreen mode

The data flows from the source into our application.

File
 ↓
Readable Stream
 ↓
Application
Enter fullscreen mode Exit fullscreen mode

Writable Stream

A writable stream is used to write data.

const stream = fs.createWriteStream("output.txt");

stream.write("Hello");
stream.write(" Node.js");

stream.end();
Enter fullscreen mode Exit fullscreen mode

The data flows from our application to the destination.

Application
    ↓
Writable Stream
    ↓
File
Enter fullscreen mode Exit fullscreen mode

Duplex Stream

A duplex stream can both read and write data.

Readable
    +
Writable
    =
Duplex
Enter fullscreen mode Exit fullscreen mode

A network socket is a common example.

Send data
    ↕
Socket
    ↕
Receive data
Enter fullscreen mode Exit fullscreen mode

Transform Stream

A transform stream reads data, modifies it, and produces new output.

Input
  ↓
Transform
  ↓
Output
Enter fullscreen mode Exit fullscreen mode

For example:

"hello"
   ↓
uppercase transformation
   ↓
"HELLO"
Enter fullscreen mode Exit fullscreen mode

Compression streams are another common example.


13. Buffers

A Buffer is used to work with raw binary data.

Computers don't only deal with text. Files, images, videos, network packets, and other data can be represented as bytes.

Node.js provides the Buffer class for handling this type of data.

For example:

const buffer = Buffer.from("Hello");

console.log(buffer);
Enter fullscreen mode Exit fullscreen mode

You may see something similar to:

<Buffer 48 65 6c 6c 6f>
Enter fullscreen mode Exit fullscreen mode

We can convert the Buffer back to text:

console.log(buffer.toString());
Enter fullscreen mode Exit fullscreen mode

Output:

Hello
Enter fullscreen mode Exit fullscreen mode

Buffers are commonly used with:

  • File systems
  • Streams
  • Network communication
  • Binary data

14. The process Object

Node.js provides a global object called process.

It represents the currently running Node.js process.

For example:

console.log(process);
Enter fullscreen mode Exit fullscreen mode

We don't normally need to import it.

Command-line arguments

console.log(process.argv);
Enter fullscreen mode Exit fullscreen mode

If we run:

node app.js hello
Enter fullscreen mode Exit fullscreen mode

Node.js makes the command-line arguments available through process.argv.

Environment variables

We can access environment variables using:

console.log(process.env);
Enter fullscreen mode Exit fullscreen mode

For example:

console.log(process.env.PORT);
Enter fullscreen mode Exit fullscreen mode

This is commonly used for configuration such as:

PORT
DATABASE_URL
API_KEY
NODE_ENV
Enter fullscreen mode Exit fullscreen mode

Exiting the process

We can explicitly terminate a process:

process.exit(1);
Enter fullscreen mode Exit fullscreen mode

Exit code 0 generally indicates successful completion, while a non-zero code commonly indicates an error.


15. What Does Non-Blocking I/O Mean?

This is one of the most important ideas in Node.js.

I/O means Input/Output.

Examples include:

  • Reading a file
  • Writing a file
  • Sending a network request
  • Receiving network data
  • Communicating with a database

A blocking approach looks conceptually like this:

Start operation
      ↓
   WAIT
      ↓
Operation finishes
      ↓
Continue
Enter fullscreen mode Exit fullscreen mode

The program cannot continue that particular execution path until the operation finishes.

Node.js is designed around a non-blocking approach.

Conceptually:

Start I/O operation
        ↓
Don't wait
        ↓
Continue executing JavaScript
        ↓
Do other work
        ↓
I/O completes
        ↓
Handle the result
Enter fullscreen mode Exit fullscreen mode

For example:

const fs = require("fs");

fs.readFile("data.txt", "utf8", (err, data) => {
    console.log(data);
});

console.log("Next task");
Enter fullscreen mode Exit fullscreen mode

The important idea is that the file operation is started, while JavaScript can continue executing other work.


16. Why Is Non-Blocking I/O Useful?

Imagine a server receives 1,000 requests.

Many of those requests may involve waiting for:

Database
File system
Network
External API
Enter fullscreen mode Exit fullscreen mode

If the server blocked while waiting for each operation, resources could be wasted waiting.

Node.js instead tries to keep the JavaScript execution moving:

Request 1 → I/O operation
Request 2 → I/O operation
Request 3 → I/O operation
Request 4 → I/O operation
       ...
       ↓
Results become ready
       ↓
Callbacks / promise continuations execute
Enter fullscreen mode Exit fullscreen mode

This makes Node.js particularly well suited to I/O-heavy applications such as APIs, web servers, real-time applications, and network services.


17. How Everything Connects

Now let's connect all the concepts.

                         Node.js
                            │
              ┌─────────────┴─────────────┐
              ↓                           ↓
             V8                         libuv
              │                           │
        Executes JS               Async operations
                                          │
                                   ┌──────┴──────┐
                                   ↓             ↓
                              Event Loop    Thread Pool
                                   │
                                   ↓
                              Callbacks
Enter fullscreen mode Exit fullscreen mode

Node.js also provides core modules:

fs       → File system
path     → File paths
os       → Operating system information
events   → Event-driven programming
streams  → Process data in chunks
buffer   → Raw binary data
process  → Current Node.js process
Enter fullscreen mode Exit fullscreen mode

All of these concepts work together to form the Node.js runtime.


Conclusion

Node.js is not simply a way to run JavaScript outside the browser. It is a runtime built around an event-driven and non-blocking I/O model.

The important concepts to remember are:

  • V8 executes JavaScript.
  • libuv provides important asynchronous I/O capabilities and the event loop implementation.
  • The event loop coordinates asynchronous callbacks.
  • Core modules provide functionality such as file-system and operating-system access.
  • Streams process data incrementally instead of requiring the entire data set to be loaded at once.
  • Buffers handle raw binary data.
  • The process object provides information and control over the running Node.js process.
  • Non-blocking I/O allows Node.js to continue doing useful work instead of unnecessarily waiting for I/O operations.

Once these concepts are clear, topics such as Express.js, HTTP servers, APIs, asynchronous programming, streams, and backend architecture become much easier to understand.

Top comments (0)