HomeData EngineeringData DIYTutorial on Nodejs Architecture

Tutorial on Nodejs Architecture

On this article we are going to get a bit deeper into the Node environment and understand how it works under the surface. The topics that will cover are:

How does the runtime environment and the JS engine relate to each other?

Nodejs is a runtime environment for running Javascript. What is a runtime environment, you might say? Let us take the example of a browser.

Every browser has a Javascript engine that converts Javascript to machine code so that the computer can understand. So, in other words, this engine is what allows us to create variables, functions and interact with the browser in a more readable way.

runtime environment is a place on your system where you can use common libraries, environment variables and actually execute your code. In the browser, for instance, you can access the document and window global variables and work with them. In Node there are also common variables that you can use in its runtime environment that we will discuss later on.

Safari, Firefox, Chrome have their own runtime environments, these environments have their own Javascript engines work respectively called Chakra, SpiderMonkey and V8. The latter is the one that we care about right now.

Besides converting Javascript code to machine code a Engine has also some other responsibilities:

  1. It provides the Garbage Collector → Used for clearing the store of variables and functions that are no longer in use in the execution (out of scope)
  2. Executes your code and converts Javascript to machine code so that the computer can understand as mentioned before
  3. Managing memory allocation
  4. Provide all the data types, operators, objects and functions
  5. Handling Call Stack (Executing your code in order)
  6. Event loop

More on what V8 does on this link

The Call Stack and Heap

The Call Stack is the very part of NodeJs that keeps track of your application’s execution. Basically it is a FILO (First in Last Out) that knows exactly where you are in the execution.

The Heap is a memory space that is allocated for your execution. Every time you are in a function scope, the variables that you declare are all stored in the V8 heap. Then, when the execution no longer needs that variable, the garbage collector is in charge of cleaning the heap for the variables of the new function.

Here is an easy example:

function multiply(n, n){
    return n * n
}function square(n){
    const resultMultiply = multiply(n, n)
    return resultMultiply
}function squaredNumber(n){
    const result = square(n)
    return result
}

The code above would fill our call stack and heap.

Below you see what happens in action:

Core Modules and C++ Bindings

Tutorial on Nodejs Architecture 1
NodeJs Architecture

This image is used everywhere but many people struggle to understand. It is actually pretty simple. Try to look at it horizontally.

  • On the top you have your Javacript code.
  • On the middle part there is the V8 engine along with core Modules, C+ + Bindings, libuv, http requests, etc.
  • And at the bottom, we have the Operating System.

The middle part is what matters for us the most right now.

Core Modules

These modules are made of some objects to enhance Javascript capabilities on the server-side. Some of the most used and some examples are below:

  • http → Used for making http requests.
http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' })
    res.end('This is a test for creating a Nodejs server')
}).listen(3000)
  • fs → Used for working with the File System (file creation, deletion, streaming and so on).
const createFile = (filePath, fileContent) => {
    fs.writeFile(filePath, fileContent, (error) => {
    if(error)
        console.error('an error occurred: ', error)
    else
        console.info('Your file is made')
})
  • path → Used for dealing with file paths
path.join(DIRNAME, 'media')

And so on… There are many functions and objects that come out of the box with Nodejs. If you want, you can explore them by downloading Node on your computer and running node , then clicking tab twice.

> node
Welcome to Node.js v13.13.0.
Type ".help" for more information.
> 
Array                 ArrayBuffer           Atomics
BigInt                BigInt64Array         BigUint64Array
Boolean               Buffer                DataView
Date                  Error                 EvalError
Float32Array          Float64Array          Function
GLOBAL                Infinity              Int16Array
Int32Array            Int8Array             Intl
JSON                  Map                   Math
NaN                   Number                Object
Promise               Proxy                 RangeError
ReferenceError        Reflect               RegExp
Set                   SharedArrayBuffer     String
Symbol                SyntaxError           TextDecoder
TextEncoder           TypeError             URIError
URL                   URLSearchParams       Uint16Array
Uint32Array           Uint8Array            Uint8ClampedArray
WeakMap               WeakSet               WebAssembly
_                     _error                assert
async_hooks           buffer                child_process
clearImmediate        clearInterval         clearTimeout
cluster               console               crypto
decodeURI             decodeURIComponent    dgram
dns                   domain                encodeURI
encodeURIComponent    escape                eval
events                fs                    global
globalThis            http                  http2
https                 inspector             isFinite
isNaN                 module                net
os                    parseFloat            parseInt
path                  perf_hooks            process
punycode              querystring           queueMicrotask
readline              repl                  require
root                  setImmediate          setInterval
setTimeout            stream                string_decoder
tls                   trace_events          tty
undefined              unescape              url
util                  v8                    vm
worker_threads        zlib

C++ Bindings

C++ Bindings refers to ways that you can use C++ code if you want to use add-ons on you project. Since Nodejs is written in C++, you can easily bind these scripts to work side by side with your Nodejs project.

But why would you use such thing?

  1. You can do it for performance reasons.
  2. You may want to integrate a third party library written in C++ to your Nodejs project.
  3. Or, maybe, access some OS APIs that are difficult to implement using Nodejs alone

Is NodeJs actually single threaded?

Have you ever heard that Nodejs is single-threaded? It is single threaded, but that is not always the case. What does that actually mean?

More on Threads…

To understand more about threads inside of systems we can look at our own computer. Our operating system executes different processes that can be executed by opening a web browser or even a calculator. These processes can contain threads. You can see thread as a line of execution that shares the same memory space inside of a process, or metaphorically…

you think of a highway lane (process). The more lanes (threads) this highway has, more cars (data) can reach their destination (be executed)

“So… if Nodejs is so fast and reliable and it is single threaded, how can it deal with so much data being sent and received at the same time?”

Asynchronous Code In Javascript

The asynchronous nature of Nodejs is what takes the credit for all of that performance boost. To simplify asynchronicity, I think that the best way to explain is:

It’s part of the code that doesn’t block the original execution flow if the response has not yet arrived. It will wait for the response to arrive and when it does, it will send the response after all the code has done with its main execution flow…

For those who had a hard time understanding there is an easier analogy:

Imagine you order a sandwich, the waiter will write down your order and send this information so the chef can start making your order. Usually the waiter will wait on someone else while your sandwich is being made. This is asynchronicity.

Now, if the waiter waited for your order to be ready to wait on someone else, then it would be a synchronous activity.

Usually on Nodejs, it is recommended that you use asynchronous whenever possible, since it greatly increases your application’s performance.

For that, Nodejs provides asynchronous callback functions or promises. Here is an example using Promises:

const getUserInfo = (userId) => new Promise((resolve, reject) => {
   const user = getUserOnDatabase(userId).then(res => {
       if(res)
         resolve(res)
       else
         reject()
})getUserInfo(userId)
    .then((res) => {
        //continues the execution here    })
    .catch(err => {})...

So what is happening here?

getUserInfo() function is grabbing some user information. However, since we created a promise to handle this request, it will take some time for the information to be retrieved. We could not block the code from executing so right after getUserInfo() is executed, the rest of the code (represented by ... ) will be executed and what is wrapped around our thenmethod will wait for the promise to resolve to execute.

So is NodeJs Single-Threaded or Multi-Threaded?

You though I was forgetting about it, don’t you? But in fact this question is not a Yes or No question but rather, “it depends”.

For some asynchronous calls the libuv library provides a thread-pool with four threads to process the code. This means that the program continues running on a single thread — let us call it the “main thread” — and delegates a separate part with four more threads for the asynchronous calls… So in this case you do have more threads running along side one another.

However, nowadays, more and more OS’s provide asynchronous interfaces for many I/O tasks and libuv does prefer that option than using the thread-pool. In this case, the main thread will prevail.

Some other libuv responsibilities are:

  1. TCP and UDP sockets of the net package
  2. Async DNS resolutions
  3. Async file and file system operations
  4. File System events

and so on… more on libuv here

Tutorial on Nodejs Architecture 2
libuv architecture

Libuv can also be called a layer that abstracts processing I/O APIs from the upper layers of Nodejs. Along with that, libuv also provides the Event Loop for the whole NodeJS runtime environment.

Event Loop? How does it work ?

Whenever you run your node process (as learned above) The Event Loop is executed. So we have 2 different parts running at the same time on the same thread: V8 and the Event Loop

Here is a little image to explain a bit the architecture behind the event loop and asynchronous events, this cycle is an abstraction of the design pattern reactor pattern:

Tutorial on Nodejs Architecture 3

Given the image above you might be asking: what the $@! is a Event Demultiplexer? What about Low Level I/O Mechanisms?

It is quite complex to explain the details but all you have to know is that it is a layer that has been implemented by numerous of operating systems so that Nodejs can interact with non-blocking asynchronous hardware I/O functionalities. From that you can already tell who is responbile for that right? Yes you are right, libuv!

Basically what happens is:

  1. you start the Nodejs process by executing something like node example.js…Then your application starts running the Event Loop.
  2. Whenever there is a asynchronous callback, promise or some asynchronous API, Node will call the libuv APIs to process these functions.
  3. These APIs will interact with low-level I/O Mechanisms.
  4. These mechanisms will send back processed functionalities to libuv (or the event demultiplexer).
  5. Then, Libuv sends the processed data (data from a file, some request that retrieved a response…etc) to its famous Event Queue — which I will explain in a bit.
  6. After retrieving data from the queue, it will be sent to the Call Stack to be executed after all synchronous code is executed.

And then, this whole cycle happens again and again until the process ends and the code stops executing.

Event Queue

coming soon…

Conclusion

Javascript is a constant learning. It is a very powerful tool given that it made on top of a very solid and fast language (c++) and can be used for several types of applications thanks to the Event Loop and the asynchronicity nature of the language. However, it is crucial that you understand how it works beneath the surface to take the most out of this powerful tool.

This article has been published from the source link without modifications to the text. Only the headline has been changed.

Source link

Most Popular