Node JS Architecture
Let us start with what Node.js actually is?
Node.js is nothing but a JavaScript runtime environment that allows developers to execute JavaScript outside web browser, generally on server side.
You might think what runtime environment means, I thought that too. When a program want to run, it would need right software and hardware to run safely and correctly, this is called a runtime environment. So in simpler words, runtime environment or RTE is the collection of software and hardware that JavaScript would need to do its job.
Node.js is composed of:
V8 Engine
Libuv Library
Node JS Bindings
V8 Engine
The V8 engine was created by Google, written in C++. It allows JavaScript code to be compiled into machine code directly. It used Just-In-Time (JIT) compilation to achieve high performance.
How the V8 engine works?
Parsing JavaScript: The source code is broken into chunks by a scanner into a format that the engine understands, here it is Abstract Syntax Tree(AST).
Compiling the code: After parsing, V8 compiles the code using JIT compilation. JIT means that the code is compiled simultaneously as it is being run. There are 2 main components to compiling Ignition (interpreter) and TurboFan (optimising compiler). Ignition converts JS code into compact byte code and runs the code quickly, while TurboFan takes "hot" (frequently executed) byte code from Ignition to generate highly optimised machine code.
Execution of the code: Once the code is compiled, the machine code is run by the CPU. V8 has 2 main memory models to execute the machine code: Stack and Heap. Stack keeps the record of the function execution. The functions are executed as last-in-first-out (LIFO). There is one stack per V8 process. Heap stores dynamic data such as objects, arrays, variables, etc.
Memory management: memory is automatically managed by V8 through garbage collection. Since we know that memory for objects, arrays, variables, etc. is stored in heap, if they are nowhere referenced in the program, they are marked as garbage. The free memory is returned to heap for reuse.
Node JS BIndings
Think of bindings as a bridge that makes V8 engine to be able to talk to the library. JavaScript cannot directly interact with file systems, networks, databases or OS operations, so a binding is needed to make the engine talk to them because the library is written in a system programming language whereas we are coding in JavaScript. Bindings 'bind' two different programming languages with each other so that code written in one language can be used in code written in some other library in some other language.
Libuv
It is a C library that actually talks to the network and helps us get the information we need. It enables Node.js to handle many concurrent operations even when JavaScript is a single threaded language. The primary functions of libuv are:
Event Loop: Manages and schedules callbacks and I/O operations. It constantly checks if the tasks have been completed and then execute the associated asynchronous callbacks on the main thread.
Thread Pool: Handles operations that cannot be performed asynchronously by the OS. The worker threads handle tasks in the background so the main event loop is not blocked.
Async I/O Handling: Manages filesystems, networking, DNS resolution and other background tasks. File system operations like reading or writing files is sent to the thread pool because OS performs many file reading/writing in a blocking way which will freeze our main JS thread.
Networking: Networking operations like HTTP requests, TCP and UDP operations are handled by OS, there are already non-blocking mechanisms such as epoll on Linux and IOCP on Windows, they notify Node.js when data is ready. No use thread pool here.
Libuv handles the asynchronous work. Thread pool and OS work simultaneously to help Node.js efficiently handle so many network connections.
Final summary,
