Intro to Nodejs
- 5 minutes read - 1004 wordsMy development background started with C
, before turning to Java
, and that’s where it stayed. Over time, I realised this was a mistake. You’ve got programming languages that are statically typed, dynamic, compiled, interpreted, object-oriented, procedural, functional and everything in between. Each of them have advantages and disadvantages and use cases where they are most appropriate. Having knowledge of only one is like believing you can solve every problem with a hammer. I decided some time ago to start learning a new language, and this ended up being JavaScript
and Node.js
. This post will briefly compare Node.js
with Java
, to help me frame it in a context I am familiar with.
What is Node.js?
To understand what is Node.js
, it’s helpful to have a brief understanding of JavaScript
itself.
JavaScript
is a general purpose scripting language that conforms to the ECMAScript
specification. This means it provides and supports all the types, values, objects, properties, functions and program syntax and semantics defined in this specification. The specification itself is standardised in ECMA-262
.
JavaScript
was originally created to run in Web browsers. The JavaScript
code is executed by a JavaScript engine within a web browser. There are a number of these engines, such as V8 in Chrome, SpiderMonkey in Firefox and Chakra in Edge. In the front-end, JavaScript
takes advantage of Web APIs provided by the browser e.g. to interact with the Document Object Model (DOM) to manipulate the content, structure and style of a website.
Node.js
decouples JavaScript
from the browser, and enables it to be run server-side. It includes a JavaScript
runtime built on Google’s V8 JavaScript engine (written in C++
), an event loop implemented in the libuv
library (written in C
), and a set of bindings to carry out tasks such as accessing files on the file system, creating HTTP and TCP/UDP servers, and other useful functions.
Node.js
also provides a package manager (NPM), that has subsequently become a JavaScript package manager. Node.js
uses a modular architecture, where modules are like libraries in C
or Java
. The package manager is used to install node modules into the node_modules folder.
Compiled vs Interpreted
I have never really got my head around these distinctions, which I think become quite blurred. When I write Java
code, I have to compile the source code using javac
into Java Virtual Machine bytecode. The JVM interprets the bytecode and executes it. To improve performance, you typically have just-in-time (JIT) compilers as part of the JVM that translate the bytecode into native machine code to improve performance.
I was always under the impression that JavaScript
was an interpreted language. This means it does not need to be compiled before it is executed, and an interpreter executes the code on the fly. There is no equivalent to javac
where a developer compiles the source code.
Digging into Node.js
, I found an excellent article with related links here, that looks at how JavaScript gets executed in the V8 runtime engine.
JavaScript
source code is parsed into an Abstract Syntax Tree (AST), which is then turned into bytecode and executed by the Interpreter, known as Ignition. As this code is being executed, profiling information is collected. This feedback is consumed by the TurboFan JavaScript compiler, which generates highly-optimised machine code.
Type Safety
Java
uses static type checking, where the type of a variable is checked at compile-time. This flags up any type errors at compilation time. For example:
String s = "hello";
s = 1;
Test.java:6: error: incompatible types: int cannot be converted to String
s = 1;
^
1 error
JavaScript
uses dynamic typing, and does not offer any type safety. This means you are free to assign types as you feel like it, which brings more flexibility but inherent danger as well. Notice how the following does not throw any errors when run:
let a = "hello";
a = 1;
var x = new Boolean(false);
x = "hello";
There is a new identifier const
which creates a read-only reference to a value. It doesn’t mean the value it holds is immutable, just that the variable identifier cannot be reassigned.
const a = "hello";
a = "world";
a = "world";
^
TypeError: Assignment to constant variable
Single-Threaded, Asynchronous and Event Driven
The official Node.js
website states it is ‘an asynchronous event driven JavaScript runtime’. It is also commonly referred to as being ‘single-threaded’. A simple way to demonstrate this is with an example, starting with Java:
public class Test {
public static void main(String args[]) throws Exception {
System.out.println("One ");
printNext();
System.out.println("Three ");
}
public static void printNext() throws Exception {
Thread.sleep(1000);
System.out.println("Two ");
}
}
Running the program above results in the following output:
One
Two
Three
The next test is to run what looks like the same flow logic in Node.js:
console.log("One ");
setTimeout(function () {
console.log("Two ");
}, 1000);
console.log("Three ");
Running the program above results in the following:
One
Three
Two
You can even set the timeout to 0, and it results in the same output. So what gives? The answer lies in the event loop.
Node.js
runs JavaScript
code in the Event Loop. This runs in a single thread, hence the statement that Node.js
is single-threaded. However, Node.js
also provides a Worker Pool implemented using the libuv library. By default, this creates a thread pool with four threads to offload asynchronous tasks to, but this number can be changed at startup time by setting the UV_THREADPOOL_SIZE environment variable. The libuv library offloads operations to the system kernel whenever possible. The official documentation describes libuv as a:
cross-platform support library which was originally written for NodeJS. It’s designed around the event-driven asynchronous I/O model.
The library provides much more than a simple abstraction over different polling I/O mechanisms: ‘handles’ and ‘streams’ provide a high level abstraction for sockets and other entities; cross-platform file I/O and threading functionality is also provided, amongst other things.
The best introduction I found for the Event Loop is from Bert Belder from one of his keynote presentations. I’ve copied the diagram below.