Lesson 1 of 14 · Node.js fundamentals
Node.js: V8 engine, event loop, versions and REPL
Objective
Understand what Node.js really is and why it suits APIs so well (online shop, bookings, delivery). Install an LTS version with nvm, and get into the habit of testing ideas in the REPL and with small scripts.
Key points
- Node.js is a JavaScript runtime environment outside the browser: the V8 engine (the one in Chrome) + the libuv library (asynchronous input/output) + a standard library (
node:fs,node:http,node:crypto…). - A Node program runs in a single process and a single JavaScript thread. It does not block during a disk read or a network request: it carries on and resumes when the answer arrives, thanks to the event loop.
- As a result, a Node server handles thousands of simultaneous connections… as long as your code does not run a long, synchronous computation that blocks everyone.
- In production, use an LTS version (even number: 22, 24…). Odd-numbered versions only live for a few months.
- nvm installs and switches Node versions per user, without touching the system. A
.nvmrcfile pins the version of a project. - The REPL (
nodewithout arguments),node -eandnode -plet you try a line of code in a few seconds.
The basics of the language (variables, functions, arrays, promises) are covered in the course "Web development: HTML, CSS and JavaScript". Here, we talk about what Node adds around it.
What Node.js contains
your JavaScript code (serveur.js)
│
┌─────────────────────┴──────────────────────┐
│ standard library: node:fs, node:http, │
│ node:path, node:crypto, node:stream… │
├──────────────────────┬──────────────────────┤
│ V8: compiles and │ libuv: event loop, │
│ runs the JS │ asynchronous I/O, │
│ (JIT, garbage │ thread pool for │
│ collector) │ disk, DNS, crypto │
│ │ │
└──────────────────────┴──────────────────────┘
operating system
- V8 turns your JavaScript into machine code on the fly (just-in-time compilation) and manages memory (garbage collector). This is why Node is fast.
- libuv provides the event loop and talks to the system (sockets, files, timers). Some slow operations (reading files, password hashing, DNS resolution) go to a small hidden thread pool, then the result comes back to the loop.
- The standard library is rich: you can write a complete HTTP server, read files, encrypt, compress… without installing anything. Built-in modules are imported with the
node:prefix (node:http), which is recommended to avoid any confusion with an npm package of the same name.
What changes compared to the browser
| Browser | Node.js |
|---|---|
window, document, the DOM |
absent: there is no page to display |
localStorage |
files (node:fs), databases |
| sandbox: no disk access | full access to the disk, the network, processes |
| JS version imposed by the visitor's browser | you choose the Node version, and therefore the available features |
globalThis = window |
globalThis = Node's global object; process gives the environment |
What exists on both sides: console, setTimeout, fetch, URL, JSON, Promise, structuredClone, crypto.randomUUID()…
The last row of the table is a real advantage: on the server, you do not need to wait for customers to update their browser. If your project runs on Node 22, all the modern syntax is available.
The first program
mkdir -p ~/essais && cd ~/essais
echo 'console.log("Marhaba depuis Node.js");' > bonjour.js
node bonjour.js # Marhaba depuis Node.js
The process object describes the running program:
// infos.js
console.log(process.version); // 'v22.x.y'
console.log(process.versions.v8); // version of the embedded V8 engine
console.log(process.platform); // 'linux', 'win32', 'darwin'
console.log(process.argv); // [path to node, path to the script, arguments…]
console.log(process.cwd()); // current directory
console.log(process.env.HOME); // environment variables
process.exitCode = 0; // exit code (0 = success)
process.argv first contains the path to node, then the path to the script, then your arguments, always as text:
node total.js 120 35.5
# process.argv = ['/usr/bin/node', '/root/total.js', '120', '35.5']
# process.argv.slice(2).map(Number) = [120, 35.5]
The event loop
Picture the server of a delivery app in Casablanca. A request arrives: "list of orders for courier 12". The server must query the database, which takes 20 ms. A "one thread per request" server keeps a thread busy for those 20 ms. Node, on the other hand, starts the database query, moves on to the next request, and resumes the first one when the database answers.
console.log('A : requête reçue');
setTimeout(() => console.log('C : la base a répondu'), 20); // simulates an I/O
console.log('B : je traite déjà la requête suivante');
// A, B, then C
(A: request received; C: the database answered; B: I am already handling the next request.)
The principle:
- Node runs your whole script from top to bottom (the synchronous code).
- Asynchronous operations (timers, network, disk) are handed over to libuv with a callback function.
- When the call stack is empty, the loop takes the completed events and calls their functions, one by one.
So even setTimeout(fn, 0) waits until the current script has finished:
console.log('1 debut');
setTimeout(() => console.log('3 minuteur'), 0);
console.log('2 fin du script');
The phases, simplified
On each iteration, the loop goes through phases in a fixed order:
| Phase | What runs |
|---|---|
| timers | the setTimeout and setInterval callbacks that are due |
| poll | completed input/output (network, files); the loop can wait here |
| check | setImmediate callbacks |
| close | close callbacks (socket.on('close')) |
Between two callbacks, Node also empties two priority queues: the process.nextTick queue, then the microtask queue (promise .then callbacks).
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
Promise.resolve().then(() => console.log('promesse'));
process.nextTick(() => console.log('nextTick'));
console.log('synchrone');
// synchrone, nextTick, promesse, then timeout and immediate
// (the order of the last two can vary in the main script)
You do not need to memorize every phase. Remember the golden rule.
The golden rule: do not block the loop
There is only one thread for your JavaScript. A 2-second computation blocks all requests for 2 seconds:
import { createServer } from 'node:http';
createServer((req, res) => {
if (req.url === '/rapport') {
const fin = Date.now() + 2000;
while (Date.now() < fin) {} // BAD: 2 s of synchronous computation
}
res.end('ok\n');
}).listen(3000);
// while one client requests /rapport, the others wait, even for '/'
Avoid in a server:
- synchronous input/output functions (
fs.readFileSync,crypto.pbkdf2Sync…) while handling a request: keep them for startup; - large loops over millions of items,
JSON.parseon huge files; - badly written regular expressions applied to user-supplied text.
The option node --trace-sync-io serveur.js prints a warning each time a synchronous API is called after startup: useful to track down blocking code before production. For truly heavy computations (PDF generation, image processing), use worker_threads or a separate service.
Node.js versions
A new major version is released every six months:
- in April: an even version (22, 24, 26…); it becomes LTS (Long Term Support) in October;
- in October: an odd version (23, 25…), maintained for only about six months, to try new features.
An LTS version goes through three states:
| State | Approximate duration | What it receives |
|---|---|---|
| Current | 6 months | all new features |
| Active LTS | 12 months | fixes, careful improvements |
| Maintenance LTS | 18 months | security fixes and critical bugs |
That is about 30 months of life. Situation at the date of this course (October 2026):
| Version | State | End of life |
|---|---|---|
| Node 20 | end of life | April 2026: migrate |
| Node 22 | Maintenance LTS | April 2027 |
| Node 24 | Active LTS | April 2028 |
| Node 26 | Current, LTS at the end of October 2026 | April 2029 |
Always check the official schedule on the Node.js website before you choose: the exact dates are published there and the pace can change. The lab for this course uses Node 22.
In practice, for a production API: the most recent active LTS, or the previous one if a library is not yet compatible. Never an odd version. Express 5, for example, requires at least Node 18.
Installing Node.js
With nvm (recommended for development)
nvm (Node Version Manager) installs Node in your home directory. Several versions coexist, without sudo.
# install nvm (official script of the nvm-sh/nvm project on GitHub;
# check the latest version on the project page)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc # reload the shell
nvm install --lts # latest LTS
nvm install 22 # a specific branch
nvm ls # installed versions
nvm use 22 # use Node 22 in this terminal
nvm alias default 22 # default version for new terminals
node --version && npm --version
In each project, a .nvmrc file pins the expected version:
echo 22 > .nvmrc
nvm use # reads .nvmrc: the whole team has the same version
On Windows, use nvm-windows or fnm, or the official installer from nodejs.org.
On a server
- Distribution packages (
apt install nodejs) are often old: check the version before you rely on them. - The NodeSource repositories provide
.deb/.rpmpackages per LTS branch, updated byapt upgrade. - In a container, start from the official
node:22-slimornode:22-alpineimage (Docker course).
The REPL and quick commands
Started without arguments, node opens the REPL (Read-Eval-Print Loop): it reads an expression, evaluates it, prints the result and starts again.
$ node
Welcome to Node.js v22.x.y.
> 120 * 1.2
144
> _ + 10 // _ = the last result
154
> const tva = 0.2
undefined
> [100, 250].map(p => p * (1 + tva))
[ 120, 300 ]
> 0.1 + 0.2
0.30000000000000004 // prices in dirhams are computed in whole centimes!
> .help // REPL commands
> .editor // enter several lines, Ctrl+D to run
> .exit // or Ctrl+D
The Tab key completes names: type fs. then Tab to see the functions of the fs module, which is loaded automatically in the REPL.
Without opening a REPL:
node -e 'console.log(new Date().toISOString())' # run one line
node -p '1500 * 0.2' # run AND print the result: 300
node -p 'process.versions' # versions of V8, libuv, OpenSSL…
node --check serveur.js # check the syntax without running
node --watch serveur.js # restart on every change (development)
Common pitfalls
- Blocking the event loop with a long computation or a
…Syncfunction inside a request: the whole server freezes, for all clients. - Using an odd or end-of-life version in production: no more security fixes. Follow the LTS.
- Installing Node with
sudo aptwithout checking the version: you may get an outdated branch. Check withnode --version. - Forgetting that
process.argvcontains text:'120' + '35'gives'12035'. Convert withNumber().
Lab
In the lab (Node 22, no network), you will:
- write and run a first script;
- record the installed Node version;
- read command-line arguments to compute a total in dirhams;
- check for yourself the execution order imposed by the event loop.
To create a file without an editor, use echo '…' > file or cat > file <<'EOF' … EOF.
Quiz
- What are the three main components of Node.js?
- Why can a Node server handle thousands of connections with a single thread?
- In what order are
console.log('a'),setTimeout(() => console.log('b'), 0)andconsole.log('c')printed, written in that order? - You are starting an API for a riad in October 2026: Node 25 or Node 24? Why?
- What is the
.nvmrcfile for?
Answers
- The V8 engine (runs the JavaScript), libuv (event loop and asynchronous input/output) and the standard library (
node:fs,node:http…). - Because input/output does not block: Node hands it over to the system, handles other requests, then resumes each task when the result arrives (event loop).
a,c, thenb: the timer only runs once the synchronous script has finished.- Node 24: it is an even LTS version, maintained until 2028. Node 25 is odd, with no long-term support.
- To pin the Node version of a project:
nvm usereads it, and the whole team works with the same version.
Reference sources: official documentation of Node.js (nodejs.org, MIT license) and Express (expressjs.com, CC BY-SA 3.0). Original MasterLinux text.