Skip to main content

Command Palette

Search for a command to run...

Node JS Architecture

Updated
6 min read
Node JS Architecture

This blog is going to be a very high level understanding of nodeJS architecture, how
Ryan Dahl came up with idea of bringing JS outside of the browser to your local enviornment.

So as we know the very famous JS engine - V8 by google (implemented in chrome), is responsible for compiling and executing JS in the browser, and libuv (an asynchronous I/O library) created for interacting with your machine's operating system. Operations like file managements systems, cryptography, or networking calls, these are CPU intensive tasks, blocking programs.

Thats where libuv comes in handling such tasks more efficiently without wasting CPU cycles, by allocating event loops and offloading such tasks with the help of worker pool(thread pool).

Both of these systems are written in c++ and c, but nodejs is written in javascript, so to create the flawless interaction between the two languages, nodeJS implemented its own NodeJS Bindings responsible for converting the JS commands to C++ commands and vice versa after receiving the responses.

So far we got the very overview of what exactly nodeJS is trying to achieve.

Executing JS on your local Enviornment using NodeJs

So as the heading suggest, how NodeJS manages to execute JS is by creating a Node process as nodeJS itself is single threaded (Its JS afterall).

This main thread initializes the project (the code file you have executed to run), each every global declarations are analyzed and executed instantly by the V8 itself if its not an asynchronous I/O task, it also imports the modules that you may have imported in your project, and registering event callbacks be it synchronous/ asynchronous.

Keep this in mind, that V8 only deals with synchronous tasks irrespective of its compute requirements from the CPU, and the asynchronous tasks are offloaded to libuv(thread pool).

Synchronous Tasks

//synchronous tasks
console.log("1st log was executed") //synchronous task

const numbers = [1,2,3]

res.forEach((num) => console.log(num) //some operations performed,which are stil sync task

console.log("2nd log was executed")

Output :

1st log was executed
1
2
3
2nd log was executed

Asynchronous Tasks

console.log("1st log was executed");

setTimeout(() => {
    console.log("Timer finished! Executing callback.");
}, 0); 

console.log("2nd log was executed");

Output:

1st log was executed
2nd log was executed
Timer finished! Executing callback.

As i mentioned V8 only executes the synchrnous tasks and offloads aync tasks to libuv, as it offloads the tasks it doesn't wait rather moves ahead and executes every synchronous tasks, after the execution of all the synchronous tasks an event loops starts up.

Event Loop Starts

uses the main thread to check event queues. if it finds a callback, it hands the main thread to V8 to execute that callback(its the response from the asynchronous callback).

So from the simplified diagram above you can see there are some stages (phases as per nodeJS documentation) present inside the Event Loop, each phase have its own queue for callbacks, after completing all the tasks specific to that particular stage it executes all the callbacks present in that queue untill the queue is exhausted or has reached its max limit.

After that the loop moves to the next phase and accordingly perform operations untill all the pending tasks/ callbacks are executed, as the event loop keeps on looping untill all the pending tasks are completed.

Let's understand each phase with the help of code snippets:

  1. Timers : functions like setTimeout() and setInterval()
console.log("1st log is executed!");

setTimeout(() => console.log("setTimeout fn executed succesfully!"),0);

console.log("2nd log is executed!");

Output:

1st log is executed!
2nd log is executed!
setTimeout fn executed succesfully!

Explaination:

As you must have observed from the output itself, setTimeout was executed at the last, since v8 firstly executed all the sync tasks and offloaded the async task to libuv which after processing was pushed to the event queue of timers phase within the event loop, as soon as V8 completed its execution the event loop starts and checks for each phase and discovers a response of timer callback and gives back the main thread to V8 to execute the callback response.

2. setImmediate: function setImmediate()

console.log("1st log is executed!");

setTimeout(() => console.log("setTimeout fn executed succesfully!"),0);

setImmediate(() => console.log("setImmediate fn executed succesfully!"));

console.log("2nd log is executed!");

Output:

1st log is executed!
2nd log is executed!
setTimeout fn executed succesfully!
setImmediate fn executed succesfully!

Explaination:

from the output you must have started observing how the event loop exactly loops through each phase, and able to identify, why was setImmediate executed after setTimeout, due to the order of the phases starting from setTimeout.

Well I got you there! wrong but also correct, because when timer and polling callbacks are not called within I/O cycle, the result becomes non-deterministic, as it completely depends on the performance of each process rather, not the actual phase within the Event Loop, so order for the two callbacks might change based on the performance.

Example of performing these operations within I/O cycle :

3. I/O Polling: filesystem operations, networking calls, os calls.

const fs = require("fs");

console.log("1st log is executed!");
setTimeout(() => console.log("setTimeout fn executed succesfully!"),5000);

fs.readFile("example.txt", "utf-8", () => console.log("file read succesfully!"));

setImmediate(() => console.log("setImmediate fn executed succesfully!"));
console.log("2nd log is executed!");

Output:

1st log is executed!
2nd log is executed!
file read succesfully!
setImmediate fn executed succesfully!
setTimeout fn executed succesfully!

Explaination:

So all the sync task were executed, now we observed 3 async tasks one related to timer, I/O Polling, and setImmediate(check), so the flow of execution within the Event loops takes place in the order of phases, but we notice the timer function's time hasn't expired yet so we move further down the line checking every phase and executing and repeating the cycle until the timer has expired.

Once the expired timer callback is received its sent back to V8 to execute the response accordingly.

So that's why you saw setTimeout's execution in the last. obviously extending the time was a clear indication of being executed in the last, but my motive behind using this example was to explain the looping that takes place in the Event Loop.

This is all you really need to know about Event Loops and if you want to dive deeper into understanding this you can refer to official NodeJS Docs (Event Loops).

Yes I haven't covered the thread pool since, I haven't learned the workings of it yet, will be updating it in the future.

Resources I used for learning:

  1. nodeJS workings by piyush garg (in hindi)

  2. offical documentaion of nodeJS

If you found this article helpful, like and share would be really appreciated! any grammatical mistakes or technical mistakes found you can contact me on my socials and convey the mssg accordingly :)

WeWritebackend

Part 1 of 1

A series im creating for jotting down my learnings from my notes to converted digital formats of blogs. this series will majorly revolve around nodeJS and expressJS and backend overall including databases.