webdev.complete
🟢 Node.js & npm
🟢The Backend
Lesson 65 of 117
20 min

Node Setup & Runtime

Install Node 24, fnm, what V8 + libuv actually do.

For 12 years, JavaScript only ran inside the browser. Then in 2009, Ryan Dahl ripped the V8 engine out of Chrome, glued it to a C library for non-blocking I/O, and called it Node. Suddenly the language you used to animate buttons could also write files, listen on sockets, and host servers. Today, almost every backend you'll touch in a JS shop runs on Node, Bun, or Deno. Let's set it up.

What is Node, really?

Node is two big things bolted together, plus a standard library:

  • V8 - Google's JavaScript engine. Same one in Chrome. Compiles your JS to native code on the fly.
  • libuv - a C library that gives V8 access to the OS: file system, network sockets, timers, child processes. Also runs the event loop.
  • Node's built-in modules - fs, http, path, crypto, and dozens more.

V8 alone can't open a file. The browser doesn't let it. Node is the runtime that hands V8 the OS keys.

Why "non-blocking" matters
A traditional server spawns one thread per request. 10,000 connections = 10,000 threads = your kernel sweating. Node uses one thread and an event loop that juggles thousands of I/O operations at once. That's the whole pitch.

The event loop in 30 seconds

You'll get a proper chapter on async later. The 30-second version: Node runs your synchronous code top to bottom. Anything async (a network call, a file read, a setTimeout) gets handed to libuv. When it finishes, its callback joins a queue. The event loop picks the next callback off the queue when the main stack is empty.

js
console.log("1");
setTimeout(() => console.log("3"), 0);
Promise.resolve().then(() => console.log("2"));
console.log("4");

// Output: 1, 4, 2, 3
// Sync runs first. Then microtasks (promises). Then macrotasks (timers).

The order isn't magic. Microtasks (promise callbacks) drain before the next macrotask (timers, I/O). Once you internalize this, async bugs stop being mysterious.

Installing Node: don't use the .pkg installer

Downloading the Node installer from nodejs.org works once. Then version 26 ships, your project needs 24, an old contract job still needs 22, and you're stuck. Use a version manager from day one.

Two good options in 2026. Pick one:

Option A: fnm (Fast Node Manager)

bash
# macOS
brew install fnm

# Linux/macOS via curl
curl -fsSL https://fnm.vercel.app/install | bash

# Add to ~/.zshrc or ~/.bashrc:
eval "$(fnm env --use-on-cd)"

# Install the latest LTS
fnm install --lts
fnm use lts-latest
fnm default lts-latest

node --version
# v24.x.x

Option B: Volta

bash
# macOS / Linux
curl https://get.volta.sh | bash

# Install Node + pin per-project
volta install node@lts
cd my-project
volta pin node@24

# Volta reads the pinned version from package.json automatically
fnm vs Volta
fnm is fast and uses a .nvmrc or .node-version file. Compatible with the old nvm ecosystem.
Volta pins the Node version inside package.json so it travels with the repo automatically. Better for teams.

Node's LTS cadence

Node has released a new major every 6 months, where even-numbered versions become LTS (Long-Term Support) in October of their release year and get maintained for about 30 months. Odd-numbered versions are short-lived and never LTS, so they do not belong in production.

The odd/even rule is being retired
Learn it anyway, because it explains every version you will meet in an existing codebase. But it is on the way out. In March 2026 the Node team announced a move to annual majors starting with Node 27 in April 2027: one release per year, promoted to LTS the following October, with a new 6-month alpha channel in front. Every major becomes LTS, so the odd/even distinction stops meaning anything. Node 26 in October 2026 is the last promotion under the old rules.
  • Node 24 ("Krypton") - Active LTS since 28 October 2025, supported until 30 April 2028. Use this for new projects.
  • Node 26 - released 5 May 2026. Still the Current line; it becomes LTS on 28 October 2026. Fine for experiments, not for a production deploy you have to babysit.
  • Node 22 ("Jod") - in maintenance since October 2025, security fixes only until 30 April 2027.
  • Node 20 - end of life 30 April 2026. Already dead.
  • Node 18 - end of life 30 April 2025. Dead longer.
Read the schedule yourself
The release dates above come from nodejs/Release, which is a JSON file the Node team keeps current. When someone in a code review claims a version is "still supported", that file settles it.

Hello, Node

Make a file, run it. That's the whole loop.

bash
mkdir hello-node && cd hello-node
echo 'console.log("Hello, " + process.version)' > hi.js
node hi.js
# Hello, v24.0.0

Or skip the file and use the REPL:

bash
node
> const x = 21
> x * 2
42
> .exit

Node runs TypeScript now

This used to need ts-node or a build step. As of Node 22.18 and 23.6 it is on by default, and it was declared stable in Node 24.12. Point Node at a .ts file and it runs.

bash
echo 'const n: number = 42; console.log(n);' > hi.ts
node hi.ts
# 42

What Node does is type stripping, not compiling. It replaces the type annotations with whitespace and runs the result. Nothing is type-checked. That distinction explains every limitation:

  • No type checking at all. A type error runs happily. You still run tsc --noEmit in CI.
  • It ignores tsconfig.json. No path aliases, no downleveling.
  • Syntax that needs real codegen errors out: enum, namespace with runtime code, parameter properties, and decorators. Node will not polyfill them.
  • import type is mandatory for type-only imports. Without the keyword Node treats it as a value import and you get a runtime error. Turn on verbatimModuleSyntax so TypeScript catches this for you.
  • Extensions are required in specifiers: import "./file.ts". And .tsx is not supported.
When you need the full thing, use tsx
For enums, decorators, path aliases, or JSX, you need a real transpiler. Node's own docs now point at tsx rather than ts-node: npm i -D tsx, then npx tsx file.ts or node --import=tsx file.ts.

Bun and Deno: the other two runtimes

Node has competition now. Both run JavaScript on the server. Both have wildly different design philosophies.

Bun

Built on JavaScriptCore (Safari's engine) instead of V8. Written in Zig. Goals: be a drop-in Node replacement that's 2-5x faster, with a built-in bundler, test runner, and TypeScript support out of the box. It hit 1.0 in September 2023 and is still on the 1.x line, now with a built-in S3 client and Postgres client too.

bash
curl -fsSL https://bun.sh/install | bash

# Bun runs Node code mostly unchanged
bun hi.js

# It also installs packages
bun install
bun add zod

Deno

Same Ryan Dahl who wrote Node, returning a decade later to fix his own regrets. Secure by default (you must opt into file/network access), TypeScript native, ships a standard library, uses Web APIs like fetch everywhere. Deno 2 arrived in late 2024 and the pitch shifted with it: Deno now markets itself as a drop-in runtime for Node developers, npm packages and all, rather than a separate ecosystem you migrate to.

bash
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh

# Run a file with explicit permissions
deno run --allow-net server.ts
Which should you pick?
For learning, stick with Node. It has the most jobs, the most tutorials, the most StackOverflow answers, and 99% of the npm ecosystem assumes it. Try Bun for the speed boost on personal projects. Try Deno when you want secure scripts or care about a Web-Standards-first runtime.

Quiz

Quiz1 / 4

What two main pieces make up the Node runtime?

Recap

  • Node = V8 + libuv + a standard library. V8 executes JS; libuv talks to the OS.
  • The event loop juggles async work on a single thread. Microtasks (promises) run before macrotasks (timers).
  • Install via fnm or Volta. Never use the raw installer.
  • Use the Active LTS version. That is Node 24 until Node 26 takes over in October 2026.
  • Bun = fast, batteries-included. Deno = secure, Web-Standards-first. Node still wins on ecosystem.
Built with Next.js, Tailwind & Sandpack.
Learn. Build. Ship.