Node.js Architecture

Himanshu MauryaHimanshu Maurya
8 min read

Node.js architecture is made to work without waiting (non-blocking), handle tasks at the same time (asynchronous), and respond to events quickly (event-driven). This makes it great for building apps that can handle lots of users at once. Let’s take a closer look at the main parts and how Node.js works.

Overview of Node.js Architecture

Node.js uses the Chrome V8 JavaScript engine and works with a single thread using an event loop. This design helps Node.js handle many users at the same time without needing to create new threads or processes for each user.

In Node.js, blocking and non-blocking refer to how operations (especially I/O) are handled—whether they wait for the result before continuing execution or not.

Blocking

The program waits for the task to finish before doing the next thing.

Eg . Reading a file synchronously using fs.readFileSync.

const fs = require('fs');

// Blocking (synchronous)
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);  // This line waits for readFileSync to finish
console.log("After reading file");

Non-Blocking

The program continues executing while the operation runs in the background, using callbacks/promises to handle the result.

Reading a file asynchronously using fs.readFile.

const fs = require('fs');

// Non-blocking (asynchronous)
fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);  // Executed later
});
console.log("After initiating readFile");  // Executes first

Why it matters in Node.js:

  • Node.js is single-threaded and event-driven.

  • Blocking operations can freeze the entire app.

  • Non-blocking is preferred for high performance and scalability, especially in I/O-heavy applications.

Core Components of Node.js Architecture

1.V8 Engine :

  • Developed by Google (used in Chrome).
  • Converts JavaScript code into machine code.

  • Extremely fast and optimized for performance.

2.Libuv :

It’s a C library that works on many platforms and provides:

  • An event loop to manage tasks

  • Asynchronous input/output (I/O) operations

  • Access to file systems and network connections like TCP/UDP

  • It hides the complex parts by running tasks in the background using threads, so you don’t have to worry about them.

3.Event Loop :

  • The heart of Node.js.

  • Handles all asynchronous callbacks.

  • Runs in a single thread but manages many I/O operations concurrently.

4.Event Queue :

The event loop takes these actions from the line and runs them one by one.

It’s a waiting line where event actions are added when their tasks finish.

5.Thread Pool :

Node.js uses a group of background threads (called a thread pool) to handle slow tasks like:

  • Reading or writing files

  • Looking up addresses on the internet (DNS)

  • Compressing data

This happens automatically in the background. By default, there are 4 threads, but you can change that number if needed.

6.Callback Queue :

  • Queue of callback functions ready to be executed.

  • Works closely with the Event Loop.

Explain the event loop using the analogy like chef handling multiple request:

  1. Imagine a single chef in a kitchen (because JavaScript is single-threaded) who is responsible for preparing all the dishes (tasks like reading files, handling clicks, making API calls).

  2. Customers (users or system events) keep placing food orders (tasks like API calls, DOM events, timeouts). These orders go into a queue called the order queue or task queue.

  3. The event loop is like a kitchen manager constantly checking:

“Is the chef done with the current dish? If yes, give him the next order from the queue.”

It ensures that once the chef finishes cooking one dish (a task), the next one gets picked up from the order queue — one at a time.

  1. Now, the chef has helpers (like waiters or timers) who can:
  • Set a timer (like setTimeout)

  • Call an API (like fetching ingredients)

  • Wait for events (like a customer calling for attention)

These helpers don't bother the chef immediately. They handle their part and, when they're done, they put a note back in the order queue:

“Chef, you can handle this now!”

Summary
A customer orders a burger → Chef starts making it.While it cooks, another customer asks for a boiled egg with a timer (like setTimeout(() => {}, 5000)).Chef finishes the burger and picks the next task from the queue (maybe another burger).Meanwhile, the timer for the egg finishes. The helper puts the egg task into the queue.Chef sees the egg task in the queue and now handles it.

Event Loop Phases

The event loop is an important concept in JavaScript that enables asynchronous programming by handling tasks efficiently. Since JavaScript is single-threaded, it uses the event loop to manage the execution of multiple tasks without blocking the main thread.

  • Call Stack: JavaScript has a call stack where function execution is managed in a Last-In, First-Out (LIFO) order.
  • Web APIs (or Background Tasks): These include setTimeout, setInterval, fetch, DOM events, and other non-blocking operations.

  • Callback Queue (Task Queue): When an asynchronous operation is completed, its callback is pushed into the task queue.

  • Microtask Queue: Promises and other microtasks go into the microtask queue, which is processed before the task queue.

  • Event Loop: It continuously checks the call stack and, if empty, moves tasks from the queue to the stack for execution.

Phase of event loop

┌─────────────────────────────┐
│   PHASE 1: timers           │ ← Executes setTimeout, setInterval
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│   PHASE 2: pending callbacks│ ← Executes system operations like TCP errors
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│   PHASE 3: idle, prepare    │ ← Internal use
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│   PHASE 4: poll             │ ← Waits for I/O (e.g., fs.readFile)
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│   PHASE 5: check            │ ← Executes setImmediate
└─────────────────────────────┘
           ↓
┌─────────────────────────────┐
│   PHASE 6: close callbacks  │ ← Executes close events (e.g., socket.on('close'))
└─────────────────────────────┘
           ↓
    Loop continues
Summary
The event loop in Node.js works like a cycle that keeps running steps one by one. It starts with the Timers Phase, where it runs code from setTimeout and setInterval. Then it goes to Pending Callbacks, which handles things like system errors. After that, it goes through the Idle/Prepare Phase, which is for Node.js's own use. Next is the Poll Phase, where it waits for things like file reading to finish and runs their code. Then comes the Check Phase, which runs code from setImmediate. After that, in the Close Callbacks Phase, it runs code when something is closed, like a socket. After all these steps, it also runs small tasks (called microtasks) like Promise.then(), and then the cycle starts again. This loop keeps going while your program is running.
console.log('Start');

setTimeout(() => {
  console.log('Timeout');
}, 0);

setImmediate(() => {
  console.log('Immediate');
});

Promise.resolve().then(() => {
  console.log('Promise');
});

process.nextTick(() => {
  console.log('Next Tick');
});

console.log('End');

Output :

Start
End
Next Tick
Promise
Timeout
Immediate

Detailed Request Processing Flow

  • Client Request: Comes via HTTP.

  • Event Loop: The main thread receives the request.

  • Routing: Node routes the request (e.g., /get-users).

  • Async Operation:

    • If it needs a database or file read, Node delegates it to the libuv thread pool.

    • The main thread continues and does not block.

  • Thread Pool: Performs the heavy I/O operation.

  • Callback Queued: Once done, the callback is pushed to the event queue.

  • Event Loop Executes Callback: When the main thread is free, it picks and executes the callback.

  • Response Sent: Final data is sent back to the client.

Illustration of Node.js Architecture

                                      EVENT LOOP

|-------------------------------------|                            WEB API
|                                     |          |--------------------------------------|
|     MEMORY HEAP    | CALL STACK |   |          |   DOM API                            |
|     |---------|    |------------|   |          |  SET TIMEOUT------------|            |
|     |         |    |    fn      |   |          | SET INTERVAL      register call back |
|     |---------|    |      fn    |   |          |  fetch()--                |          |
|                    | global     |   |          |           |               |          |
|                    |------------|   |          |           |promise        |          |
|                          |    |high priority   |   |-----------------|     |          |
|                          |     -----|----------|---|  | CB|  |CB|    |     |          |
|--------------------------|----------|          |   |-----------------|     |          |
                           |                      ---------------------------|----------|
                           |                 ---------------------           |
                           |---------------- |      |CB|  |CB|    |----------|
               add to call stack             |--------------------|
                    event loop                 task Queue
console.log("Start of script "); // 1

setTimeout(() => {
  console.log("A"); // 4
}, 0);

setTimeout(() => {
  console.log("B"); // 5
}, 0);

setTimeout(() => {
  console.log("c"); // 6
}, 2 * 1000); // 2 seconds

console.log("End of script"); // 2
console.log("Bye Bye"); // 3

Output

Start of script 
End of script
Bye Bye
A
B
c

Async code in node.js : callback and promises

In Node.js, asynchronous (async) code is crucial because I/O operations like reading files, accessing databases, or making HTTP requests can take time. Async patterns ensure the server doesn't block while waiting for these operations.

1. Callbacks

A callback is a function you give to another function, so it can run after something is done, like reading a file or waiting for a timer.

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }
  console.log('File contents:', data);
});

Problems with Callbacks (Callback Hell)

When you have many nested async operations, the code becomes deeply nested and hard to manage:

loginUser("user123", "password", function(err, user) {
  if (err) {
    console.log("Login failed:", err);
  } else {
    fetchUserData(user.id, function(err, data) {
      if (err) {
        console.log("Error fetching data:", err);
      } else {
        saveToDatabase(data, function(err, response) {
          if (err) {
            console.log("Error saving data:", err);
          } else {
            console.log("All done! Data saved:", response);
          }
        });
      }
    });
  }
});

2. Promises

A Promise represents a value that may be available now, in the future, or never. It's a cleaner alternative to callbacks.

function asyncTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const success = true;
      if (success) {
        resolve('Task completed');
      } else {
        reject('Task failed');
      }
    }, 1000);
  });
}

Consuming a Promise

asyncTask()
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error);
  });

Bonus: async/await (Built on Promises)

Modern and cleaner way to write async code.

async function run() {
  try {
    const result = await asyncTask();
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

run();

I’m truly thankful for your time and effort in reading this.

10
Subscribe to my newsletter

Read articles from Himanshu Maurya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Himanshu Maurya
Himanshu Maurya

Hi, Thank-you for stopping by and having a look at my profile. Hi! I’m a web developer who loves working with the MERN stack . I enjoy making interactive and user-friendly websites and webapps. I’m great at taking ideas and bringing them to life through coding!