NodeJSIntroduction Chapter1
NodeJSIntroduction Chapter1
NodeJSIntroduction Chapter1
Organizations that are thinking of adding a full-stack developer to their team should
consider the following drawbacks. First, most full-stack developers are not an expert
in any skill and instead have average knowledge of multiple skills. Therefore, if a
project requires detailed experience with complex concepts, hiring multiple, more
for a project places a high reliance or risk on that person, and would be detrimental to
User
Browser
Browser-to-Web Server Communication
Rendering the Browser View
User Interaction
Web Server
Back-end Services
The single worker thread featured in browsers was already
quite successful in the server space. Here,
the popular nginx web server showed that the event loop
pattern (explained later in this chapter) was actually a
blessing for performance – eliminating the need to use a
dedicated thread pool for handling requests.
The idea of packaging everything in a file-centric
structure called modules. This allowed Node.js to avoid many
of the pitfalls of other languages and frameworks – including
JavaScript in the browser.
The idea of avoiding creating a huge framework and leaving
everything extensible and easy to get via package managers.
Node.js-to-Angular Stack Architecture
Understanding the Node.js-to-Angular
Stack Components
Node.js
Node.js Advantages
Node.js Advantages
ANGULAR
Angular Advantages
MongoDB
MongoDB Advantages
MongoDB Advantages
Express
Express Advantages
Summary of Introduction
Threads
Modern computers offer a lot of computing power. However, for an application to really use the available
computing power, we need to have multiple things working in parallel. Modern operating systems know
about different independently running tasks via so-called threads. A thread is a group of operations running
sequentially, which means in a given order. The operating system then schedules when threads run and where
(i.e., on which CPU core) they are placed.
These principles together form a platform that seems easy to create, but hard to replicate. After all, there are
plenty of JavaScript engines and useful libraries available. For Ryan Dahl, the original creator and maintainer
of Node.js, the basis of the framework had to be rock solid.
Ryan Dahl selected an existing JavaScript engine (V8) to take over the responsibility of parsing and running
the code written in JavaScript. The V8 engine was chosen for two good reasons. On the one hand, the engine
was available as an open source project under a permissive license – usable by projects such as Node.js. On
the other hand, V8 was also the engine used by Google for its web browser Chrome. It is very fast, very
reliable, and under active development.
One of the drawbacks of using V8 is that it was written in C++ using custom-built tooling called GYP.
While GYP was replaced in V8 years later, the transition was not so easy for Node.js. As of today, Node.js is
still relying on GYP as a build system. The fact that V8 is written in C++ seems like a side note at first, but
might be pretty important if you ever intend to write so-called native modules.
Native modules allow you to go beyond JavaScript and Node.js – making full use of the available hardware
and system capabilities. One drawback of native modules is that they must be built on each platform. This
is against the cross-platform nature of Node.js.
The most important component in Node.js’s architecture – besides the JavaScript engine
– is the libuv library. libuv is a multi-platform, low-level library that provides support for
asynchronous input/output (I/O) based on an event loop. I/O happens in multiple forms,
such as writing files or handling HTTP requests. In general, I/O refers to anything that is
handled in a dedicated area of the operating system.
Any application running Node.js is written in JavaScript or some flavor of it. When
Node.js starts running the application, the JavaScript is parsed and evaluated by V8. All
the standard objects, such as console, expose some bindings that are part of the Node.js
API. These low-level functions (such as console.log or fetch) make use of libuv. Therefore,
some simple script that only works against language features such as primitive
calculations (2 + 3) does not require anything from the Node API and will remain
independent of libuv. In contrast, once a low-level function (for example, a function to
access the network) is used, libuv can be the workforce behind it.
In Figure 1.2, a block diagram illustrating the various API layers is shown. The beauty of
this diagram is that it reveals what Node.js actually is: a JavaScript runtime allowing
access to low-level functionality from state-of-the-art C/C++ libraries. The Node.js API
consists of the included Node.js bindings and some C/C++ addons:
Understanding the event loop
An event loop is a runtime model that enables users to run all operations from a single thread –
irrespective of whether the operations access long-running external resources or not. For this to
work, the event loop needs to make requests to an event provider, which calls the specified event
handlers. In Node.js, the libuv library is used for event loop implementation.
The reason for giving libuv the most space in Figure 1.1 is to highlight the importance of this library.
Internally, libuv is used for everything regarding I/O, which arguably is the most crucial piece of any
framework. I/O lets a framework communicate with other resources, such as files, servers, or
databases. By default, dealing with I/O is done in a blocking manner. This means that the sequence
of operations in our application is essentially stopped, waiting for the I/O operation to finish.
Two strategies for mitigating the performance implications of blocking I/O exist.
The first strategy is to create new threads for actually performing these blocking I/O operations.
Since a thread contains an independent group of operations, it can run concurrently, eventually not
stopping the operations running in the original thread of the application.
The second strategy is to not use blocking I/O at all. Instead, use an alternative variant, which is
usually called non-blocking I/O or asynchronous I/O. Non-blocking I/O works with callbacks, that is,
functions that are called under certain conditions – for instance when the I/O operation is finished.
Node.js uses libuv to make extensive use of this second strategy. This allows Node.js to run all code
in a single thread, while I/O operations run concurrently.
In addition to the different I/O operations, the library comes with a set of different
options for handling asynchronous user code.
The event loop itself follows the reactor design pattern. Wikipedia describes the
pattern as follows:
The reactor design pattern is an event handling pattern for handling service requests
delivered concurrently to a service handler by one or more inputs. The service handler
then demultiplexes the incoming requests and dispatches them synchronously to the
associated request handlers. (https://en.wikipedia.org/wiki/Reactor_pattern)
Importantly, this definition mentions synchronous dispatch. This means that code that
is run through the event loop is guaranteed to not run into any conflicts. The event loop
makes sure that code is always run sequentially. Even though the I/O operations may
concurrently run, our callbacks will never be invoked in parallel. From our perspective,
even though Node.js will internally (through libuv) use multiple threads, the whole
application is single-threaded.
The following is a simple script that shows you the basic behavior of the event loop at
play – we’ll discuss how to run this in the Using Node.js from the
command line section:
events.js
console.log('A [start]');
setTimeout(() => console.log('B [timeout]'),
0);
Promise.resolve().then(() => console.log('C
[promise]'));
console.log('D [end]');
While the code snippet only deals with JavaScript-related constructs
(such as console, Promise, and setTimeout), in general, the callbacks are
associated with resources that go beyond Node.js, such as file system
changes or network requests. Some of these resources may have an
operating system equivalent; others only exist in form of blocking I/O.
Consequently, the event loop implementation always considers its
thread pool and polls for progressed I/O operations. Timers (such
as setTimeout in the example script) are only run in the beginning. To
know whether a timer needs to be run, its due time is compared with the
current time. The current time is synced with the system time initially. If
there is nothing to be done anymore (that is, no active timer, no resource
waiting to finish, etc.), then the loop exits.
Let’s see how we can run Node.js to solidify our knowledge about
the event loop.
Using Node.js from the command line
Using JavaScript for a web application just requires you to
open the website in a browser. The browser will evaluate the
included JavaScript and run it. When you want to use
JavaScript as a scripting language, you need to find a new
way of running JavaScript. Node.js offers this new way –
running JavaScript in a terminal, inside our computer, or
from a server.
When Node.js is installed, it comes with a set of command-
line tools that will be available in the terminal of your choice.
For this book, you’ll need to know about three different
executables that we’ll use throughout the chapters:
node: The main application to run a Node.js
script
npm: The default package manager – more
on that later
npx: A very convenient utility to run npm
binaries
For now, we only need to know about node. If
we want to run the events.js script from the
previous section, we need to execute the
following command in the directory in which
the script (events.js) has been placed. You can
place it there by just inserting the content
from the previous events.js listing:
$ node events.js
A [start]
D [end]
C [promise]
B [timeout]
The command is shown after the conventional $ sign indicating the command prompt. The
output of running the script is shown below the node events.js command.
As you can see, the order is “A D C B” – that is, Node.js first handled all the sequential
operations before the callbacks of the promise were handled. Finally, the timeout
callback was handled.
The reason for handling the promise callback before the timeout callback lies in the event
loop. In JavaScript, promises spawn so-called micro tasks, which are placed in the pending
callback section of the libuv event loop from Figure 1.4. The timeout callback, however, is
treated like a full task. The difference between them lies within the event loop. Micro tasks
are placed in an optimized queue that is actually peeked multiple times per event loop
iteration.
According to libuv, the timeout callback can only be run when its timer is due. Since we only
placed it in the event loop during the idle handles (i.e., main section) of the event loop, we
need to wait until the next iteration of the event loop.
The node command-line application can also receive additional parameters. The official
documentation goes into all details (https://nodejs.org/api/cli.html). A helpful one is -
e (short version of --eval) to just evaluate a script directly from the command-line input
without requiring a file to run:
$ node -e "console.log(new Date())"
2022-04-29T09:20:44.401
Another very helpful command line flag is --
inspect. This opens the standard port for
graphical inspection, for example, via the
Chrome web browser.
Let’s run an application with a bit of
continuous logic to justify an inspection
session. In the terminal on your machine,
run the following:
$ node -e "setInterval(() =>
console.log(Math.random()), 60 * 1000)" --inspect
Debugger listening on
ws://127.0.0.1:9229/64c26b8a-0ba9-484f-902d-
759135ad76a2
const b = require('./b');
console.log('The value of b is:', b.myValue);
The call to./b.js has been replaced by ./b. This will still work, as Node.js will try
various combinations for the given import. Not only will it append certain
known extensions (such as .js) but it will also look at whether b is actually a
directory with an index.js file.
Therefore, with the preceding code, we could actually move b.js from a file
adjacent to a.js to an index.js file in the adjacent directory, b.
The greatest advantage, however, is that this syntax also allows us to import
functionality from third-party packages. As we will explore later in Chapter 2
, Dividing Code into Modules and Packages, our code has to be divided into
different modules and packages. A package contains a set of reusable modules.
Node.js already comes with a set of packages that don’t even need to be
installed. Let’s see a simple example:
host.js
const os = require('os'); console.log('The current hostname is:', os.hostname());
The preceding example uses the
integrated os package to obtain the current
computer’s network name.
We can run this script with node in
the command line:
$ node host.js