Appendix B

From the Browser & Userland Libraries

Many habits and packages from older Node code or from front-end work have built-in replacements in modern Node. This appendix is a migration guide: browser JavaScript β†’ Node, and the most common npm packages β†’ the features that now ship with Node. Fewer dependencies mean fewer updates, fewer vulnerabilities and faster installs.

You will learn

  • How browser concepts map to Node (window, DOM, storage, modules).
  • Built-in replacements for chalk, axios, dotenv, nodemon, Mocha/Jest, uuid, rimraf, glob, yargs and more.
  • How the Node.js "userland migrations" codemods automate some of this.
  • What WebAssembly and Node-API (native addons) are, and when you'd meet them.

Browser β†’ Node cheat sheet

In the browserIn Node
windowglobalThis (works in both)
document, the DOMnone β€” use a template string or library (jsdom for tests)
localStoragea file (fs), node:sqlite or a database
<script type="module">"type": "module" in package.json
location.searchnew URL(req.url, base).searchParams (server), process.argv (CLI)
fetch, URL, AbortController, TextEncoder, structuredClone, WebSocket, crypto.subtle, Blob, web streamsthe same globals
console.log β†’ DevToolsconsole.log β†’ stdout; --inspect for DevTools
user input: forms, eventsprocess.stdin, readline, HTTP request bodies

🧠 Analogy: moving from a hotel to your own flat. In the browser you're a hotel guest: lots is provided (the DOM, storage, a window to look out of), but you can't touch the building (no file system, no raw sockets). Node is your own flat: no room service, but you have the keys to everything β€” and responsibility for locking the doors (security, Chapter 15).

Packages you may not need anymore

PackageBuilt-in replacementChapter
chalk, kleur, ansi-colorsutil.styleText2
dotenvnode --env-file=.env, process.loadEnvFile()2
nodemonnode --watch2
yargs, minimist, commander (simple cases)util.parseArgs2
axios, node-fetch, got, requestfetch12
ws (client)WebSocket12
mocha, jest, sinon (basic use)node:test, node:assert, mock14
nyc, c8--experimental-test-coverage14
uuidcrypto.randomUUID()β€”
rimraf, mkdirp, ncpfs.rm({ recursive }), fs.mkdir({ recursive }), fs.cp9
glob, fast-globfs.glob9
lodash.clonedeepstructuredCloneβ€”
sqlite3, better-sqlite3 (simple use)node:sqlite16
ts-node (for erasable TS)node file.tsApp. A

Big frameworks (Express, Fastify, Prisma, Zod…) still earn their place. The candidates for removal are small utilities that only filled gaps Node has since closed.

Before and after

chalk β†’ util.styleText

❌ Before: a dependency

βœ… After: built in

styleText respects NO_COLOR, FORCE_COLOR and whether the output is a terminal (here it's captured, so no colour codes).

axios β†’ fetch

The three differences to remember: stringify bodies and set headers yourself, check res.ok, parse the body explicitly. A 10-line wrapper (Chapter 12) restores the convenience.

dotenv, uuid, rimraf, structuredClone

Mocha β†’ node:test

Mocha-style code often needs only the imports changed: describe, it, before, after, beforeEach and afterEach exist in node:test. Replace chai's expect(x).to.equal(y) with assert.equal(x, y), sinon spies with mock.fn(), and the mocha command with node --test. Jest users map jest.fn() β†’ mock.fn(), jest.useFakeTimers() β†’ t.mock.timers.enable(), and expect(...).toEqual β†’ assert.deepEqual.

Automated migrations

The Node.js project maintains userland migrations: codemods that rewrite your code automatically, for example from chalk to util.styleText, from axios to fetch, from Mocha to the test runner, or to add correct .ts extensions to TypeScript imports. They run with the codemod tool:

Always run codemods on a clean git working tree, review the diff, and run your tests afterwards.

WebAssembly in Node

WebAssembly (Wasm) is a compact binary format that languages like Rust, C and Go can compile to. Node runs it with the same WebAssembly API as browsers, so you can use a fast image codec or parser written in another language without native compilation. This tiny module (written in bytes, normally produced by a compiler) exports an add function:

Native addons and Node-API

Some packages include C or C++ code compiled into a native addon (a .node file): database drivers, image processing (sharp), cryptography. Node-API is the stable C interface they use; its ABI stability means an addon compiled once keeps working across Node versions. As an app developer you mostly meet addons when npm install needs build tools (node-gyp, Python, a C++ compiler) or downloads prebuilt binaries for your platform. Prefer pure-JavaScript or WebAssembly packages when performance allows: they install anywhere. And under the permission model, addons must be explicitly allowed (--allow-addons).

More examples

yargs-style options with parseArgs

moment β†’ Intl

Recap

  • Browser β†’ Node: globalThis instead of window, no DOM or storage, the same web APIs (fetch, URL, streams, WebSocket), files and processes instead.
  • Built-ins replace many small packages: styleText, --env-file, --watch, parseArgs, fetch, node:test, randomUUID, fs.rm/mkdir/cp/glob, structuredClone, node:sqlite.
  • Official codemods automate common migrations; review the diff and run the tests.
  • WebAssembly runs portable compiled code; native addons use Node-API and need build tools or prebuilt binaries.

Exercises

Exercise 1 🟒 easy map it

Name the built-in replacement for: (a) nodemon app.js; (b) require("dotenv").config(); (c) uuidv4(); (d) rimraf dist; (e) chalk.yellow("warn").

Show solution

(a) node --watch app.js; (b) node --env-file=.env app.js or process.loadEnvFile(); (c) crypto.randomUUID(); (d) fs.rm("dist", { recursive: true, force: true }); (e) util.styleText("yellow", "warn").

Exercise 2 🟒 easy browser code

Which lines of this browser snippet fail in Node, and why? const theme = localStorage.getItem("theme"); const url = new URL(location.href); document.title = "Hi"; const res = await fetch("/api");

Show solution

localStorage, location and document don't exist in Node (ReferenceError). fetch exists, but "/api" is a relative URL with no page to resolve it against, so it throws an "invalid URL" TypeError β€” Node needs absolute URLs. new URL(...) itself works if given an absolute string.

Exercise 3 🟒 easy convert

Rewrite with built-ins: const copy = _.cloneDeep(settings); const id = uuid.v4(); console.log(chalk.green(`saved ${id}`));

Show solution

Exercise 4 🟒 easy explain

Give two reasons to remove a small utility dependency when Node has a built-in equivalent, and one reason you might keep it.

Show solution

Remove: fewer packages to audit and update (smaller supply-chain risk), and faster, smaller installs. Keep: when the package does meaningfully more than the built-in (e.g. yargs' generated help and sub-commands, axios interceptors used everywhere), or when you must support older Node versions that lack the built-in.

Exercise 5 🟑 medium axios habits

Write http.js exporting api.get(url) and api.post(url, body) that behave like axios: JSON in and out, throw on non-2xx with err.response = { status, data }, and a default 5 s timeout. Test it against a local server.

Show solution

Exercise 6 🟑 medium Jest β†’ node:test

Translate this Jest test to node:test: const cb = jest.fn(); jest.useFakeTimers(); setTimeout(cb, 1000); jest.advanceTimersByTime(1000); expect(cb).toHaveBeenCalledTimes(1);

Show solution

Exercise 7 🟑 medium glob

Replace glob.sync("src/**/*.{js,ts}", { ignore: "**/*.test.*" }) with fs.glob, returning a sorted array.

Show solution

Exercise 8 🟑 medium a storage shim

Browser code uses localStorage. Write a file-backed createStorage(file) for Node with the same getItem/setItem/removeItem API (values are strings), saving synchronously so it behaves like the browser API.

Show solution

Sync writes are fine for a CLI or a test helper; in a server you'd use async I/O or a database.

Exercise 9 πŸ”΄ hard audit

You inherit a package.json with: express, dotenv, nodemon, axios, chalk, uuid, mocha, chai, sinon, rimraf, zod, pg. Which would you remove, keep or replace, and in what order would you do it safely?

Show solution

Keep: express (framework), zod (validation), pg (database driver) β€” no built-in equivalents. Replace: dotenv β†’ --env-file; nodemon β†’ --watch; chalk β†’ styleText; uuid β†’ randomUUID; rimraf β†’ fs.rm; axios β†’ fetch (with a small wrapper); mocha/chai/sinon β†’ node:test/assert/mock. Order: dev-only tooling first (nodemon, rimraf, dotenv in scripts: zero runtime risk), then trivial runtime swaps (uuid, chalk), then the test stack (migrate files gradually; both runners can coexist), and axios last (most behaviour differences: errors, JSON, timeouts). One change per commit, tests green after each.

Exercise 10 πŸ”΄ hard WebAssembly

Modify the WebAssembly example's bytes so the exported function multiplies instead of adds (the opcode for i32.mul is 0x6c), and export it under the name mul. Call it with 6 and 7.

Show solution

The name is three bytes long like "add", so no section sizes change. Real projects never write bytes by hand: wat2wasm (text format) or a compiler (Rust's wasm-pack, Emscripten) produces them.

Official docs