Appendix B ยท Refresher

Node.js, npm & DevTools

The language is only half the job; the other half is running, testing, debugging and packaging code. This appendix refreshes the Node, npm and browser-DevTools skills the chapters rely on.

You will refresh

  • The Node REPL, running files, --watch, -e/-p and --env-file.
  • package.json, npm commands, semantic versioning and lockfiles.
  • The built-in test runner (node:test) and assert.
  • process, built-in node: modules, and exit codes.
  • Debugging in Node and in browser DevTools, plus the console's extra methods.

1. The Node REPL

Run node with no arguments. Useful commands: .help, .editor (multi-line paste mode, finished with Ctrl+D), .exit. _ holds the last result, Tab completes names, and top-level await works.

In this book: REPL blocks (>) leave out the undefined echoes after declarations (Chapter 1).

Quick check: why does the REPL print undefined after const x = 1?

It prints the completion value of every input, and a declaration statement has none (undefined).

2. Running code

CommandDoes
node app.jsruns a file
node --watch app.jsre-runs when files change
node -e "code" / node -p "expr"runs code / prints an expression's value
node --env-file=.env app.jsloads KEY=value lines into process.env
node --testruns *.test.js files (section 6)
node --inspect app.jsenables the debugger (section 9)

In this book: every example runs with node file.js; Chapter 16's CLI uses a shebang and "bin".

Quick check: what's the difference between -e and -p?

-e evaluates code; -p evaluates it and prints the result.

3. package.json

FieldMeaning
type"module": .js files are ES modules (Chapter 14)
scriptscommands for npm run <name>; npm start and npm test are shortcuts
dependencies / devDependenciesneeded at run time / only for development (tests, linters, build)
enginessupported Node versions
bin, exportscommands to install / the public entry points of the package

In this book: the playground and the Chapter 16 project both use "type": "module".

Quick check: where does a test framework like Vitest belong?

In devDependencies, because it's not needed when the app runs in production.

4. npm and semantic versioning

CommandDoes
npm init -ycreates package.json
npm install zod / npm i -D prettieradds a dependency / a dev dependency
npm cia clean install exactly as recorded in package-lock.json (CI and servers)
npm run devruns a script
npx eslint .runs a package's command without installing it globally
npm outdated / npm updateshows / applies allowed updates

Versions are MAJOR.MINOR.PATCH: a breaking change, a new feature, a fix. ^3.23.8 allows >=3.23.8 <4.0.0, and ~3.3.3 allows >=3.3.3 <3.4.0. Commit package-lock.json, and never commit node_modules/.

In this book: nothing needs installing; every example uses only built-in modules.

Quick check: does ^1.4.2 allow version 1.9.0? 2.0.0?

1.9.0 yes (same major version); 2.0.0 no.

5. Testing with node:test

node --test finds files named *.test.js (and a few other patterns), runs them in parallel, and reports the results. node:test also offers beforeEach, mock.fn(), mock.timers, snapshots and --watch. Vitest and Jest are popular alternatives with the same ideas.

In this book: Chapter 16 tests the order processor this way.

Quick check: why import node:assert/strict rather than node:assert?

The strict version uses === and strict deep equality by default; the legacy one uses ==.

6. process and exit codes

Prefer process.exitCode = n over process.exit(n), which stops immediately, even with output still being written.

In this book: Chapter 16 sets process.exitCode = 1 on fatal errors.

Quick check: what's in process.argv[2] for node tool.js --fast?

"--fast". Index 0 is the node binary and index 1 is the script.

7. Built-in modules

ModuleFor
node:fs/promises, node:fsfiles: readFile, writeFile, streams
node:path, node:urljoin, basename, fileURLToPath, pathToFileURL
node:utilparseArgs, promisify, inspect, isDeepStrictEqual
node:timers/promisesawaitable, abortable setTimeout
node:child_process, node:worker_threadsrun programs / CPU work in parallel
node:events, node:readline, node:httpemitters, line input, servers

In this book: Chapters 12โ€“16 use fs/promises, timers/promises, readline, util and path.

Quick check: why write "node:fs" instead of "fs"?

The node: prefix makes it unambiguous that it's a built-in, and an npm package called fs can never shadow it.

8. More than console.log

(The assertion message goes to stderr, so it isn't in the stdout listing above.) Also useful: console.time("x")/console.timeEnd("x"), console.trace() and console.dir(obj, { depth: null }).

In this book: Chapter 16 sends progress to console.error, which is stderr.

Quick check: which console method shows the full nested structure of a deep object?

console.dir(obj, { depth: null }). By default Node only prints 2 levels deep.

9. Debugging

  1. Put a debugger; statement where you want to stop, or set a breakpoint in your editor.
  2. Run node --inspect-brk app.js, which pauses on the first line.
  3. Open chrome://inspect in Chrome (or use VS Code's JavaScript Debug Terminal), and step through the code, inspecting variables and the call stack.

In the browser: DevTools (F12) has a Console (where $0 is the selected element, copy(x) copies a value, and "Live expressions" are available), Sources (breakpoints, conditional breakpoints, logpoints, the call stack and scopes), and Network (requests, timing, throttling).

In this book: try it on Chapter 12's event-loop examples by setting breakpoints inside the then callbacks.

Quick check: what's a logpoint?

A breakpoint that logs a message instead of pausing. It's like adding a console.log without editing the code.

10. Formatting, linting and types

In this book: the code follows Prettier-style formatting; types are a natural next step after Chapter 16.

Quick check: does // @ts-check change how Node runs the file?

No. It's a comment that editors and tsc read; the runtime ignores it.

11. Managing Node versions

Node has a new major release every 6 months; even-numbered versions become LTS (long-term support), which is what you use in production. Version managers such as fnm, nvm or volta install several versions side by side, and a .nvmrc or .node-version file pins a project's version.

In this book: everything was verified on Node 26.13 (LTS). A few features (using, Temporal, RegExp.escape) need 24+ or 26+.

Quick check: should a production server run Node 27?

Preferably not: odd-numbered releases never become LTS. Use the current even-numbered LTS line.

Recap: tool โ†’ where you see it in this book

ToolUsed forWhere
Node REPLquick experimentsREPL blocks in every chapter
node file.js, -e/-prunning examplesevery chapter
"type": "module"ES modules by defaultCh. 1, 14, playground
npm scriptsnpm start / npm testCh. 16
node:test + assert/stricttestsCh. 16
process.argv/exitCode, parseArgscommand-line toolsCh. 14, 16
fs/promises, timers/promises, readlineasync I/OCh. 12, 13, 16
--inspect, DevToolsdebuggingany chapter

Exercises

Exercise 1 ๐ŸŸข easy build it ยท playground

In playground/, run npm init -y, set "type": "module", and add scripts start (runs hello.js) and test (runs node --test).

Show solution

Then npm start and npm test work. (The book's playground already ships with this file.)

Exercise 2 ๐ŸŸข easy predict the output

Which versions satisfy "^2.3.1": 2.3.0, 2.3.9, 2.10.0, 3.0.0?

Show solution

2.3.9 and 2.10.0. ^ allows any version from 2.3.1 up to (but not including) 3.0.0.

Exercise 3 ๐ŸŸข easy explain why

Why should CI servers run npm ci instead of npm install?

Show solution

npm ci installs exactly what package-lock.json records, fails if the lockfile and package.json disagree, and starts from a clean node_modules. Builds are reproducible. npm install may update the lockfile.

Exercise 4 ๐ŸŸข easy build it

Print the current Node version's major number with a single node -p command.

Show solution

-p prints the value of the expression.

Exercise 5 ๐ŸŸก medium build it ยท playground

Write playground/money.test.js testing formatPrice from Chapter 6 (playground/format.js): the USD output, the JPY output (no decimals), and that the formatter cache returns the same result twice.

Show solution

For JPY, assert.match avoids depending on the exact yen symbol and spacing, which can differ between ICU versions.

Exercise 6 ๐ŸŸก medium fix the bug

Sometimes the last lines of output go missing when this script fails. Why? Fix it.

Show solution

When stdout is a pipe, writes can be asynchronous, and process.exit() terminates immediately, possibly before they're done. Setting exitCode lets Node finish naturally.

Exercise 7 ๐ŸŸก medium build it

Write a tiny CLI, greet.js, using parseArgs: --name (required), --times/-t (default 1) and --shout (a boolean). Print the usage to stderr with exit code 2 when --name is missing.

Show solution

parseArgs returns strings for string options; converting to a number is your job.

Exercise 8 ๐ŸŸก medium explain why

Why does an even-numbered Node release (24, 26) suit production better than an odd-numbered one?

Show solution

Even releases enter Long-Term Support, with about 30 months of fixes and security patches. Odd releases are supported only briefly, to try out new features.

Exercise 9 ๐Ÿ”ด hard build it

Use mock.fn() from node:test to test that a notify(order, send) function calls send exactly once, with the right message, for paid orders, and never for failed ones.

Show solution

Passing send as a parameter (dependency injection) is what makes it easy to replace with a mock, as Chapter 16 does with pay.

Exercise 10 ๐Ÿ”ด hard mini-project ยท playground

Set up a complete mini project in playground/word-count/: a package.json (ESM, a bin, and start/test scripts), src/count.js exporting countWords(text) (returning a Map sorted by frequency), src/cli.js reading a file given as an argument (with a usage message and exit code 2), and test/count.test.js.

Show solution

It covers the whole toolchain: ESM package.json with bin and scripts, Unicode-aware regex, a Map sorted with toSorted, top-level await, exit codes, and node:test.

Official docs