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/-pand--env-file. package.json, npm commands, semantic versioning and lockfiles.- The built-in test runner (
node:test) andassert. process, built-innode: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
| Command | Does |
|---|---|
node app.js | runs a file |
node --watch app.js | re-runs when files change |
node -e "code" / node -p "expr" | runs code / prints an expression's value |
node --env-file=.env app.js | loads KEY=value lines into process.env |
node --test | runs *.test.js files (section 6) |
node --inspect app.js | enables 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
| Field | Meaning |
|---|---|
type | "module": .js files are ES modules (Chapter 14) |
scripts | commands for npm run <name>; npm start and npm test are shortcuts |
dependencies / devDependencies | needed at run time / only for development (tests, linters, build) |
engines | supported Node versions |
bin, exports | commands 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
| Command | Does |
|---|---|
npm init -y | creates package.json |
npm install zod / npm i -D prettier | adds a dependency / a dev dependency |
npm ci | a clean install exactly as recorded in package-lock.json (CI and servers) |
npm run dev | runs a script |
npx eslint . | runs a package's command without installing it globally |
npm outdated / npm update | shows / 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
| Module | For |
|---|---|
node:fs/promises, node:fs | files: readFile, writeFile, streams |
node:path, node:url | join, basename, fileURLToPath, pathToFileURL |
node:util | parseArgs, promisify, inspect, isDeepStrictEqual |
node:timers/promises | awaitable, abortable setTimeout |
node:child_process, node:worker_threads | run programs / CPU work in parallel |
node:events, node:readline, node:http | emitters, 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
- Put a
debugger;statement where you want to stop, or set a breakpoint in your editor. - Run
node --inspect-brk app.js, which pauses on the first line. - Open
chrome://inspectin 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
- Prettier formats code automatically, ending style debates.
- ESLint finds bugs and bad patterns, such as unused variables,
==, missingawait, or floating promises (with a TypeScript plugin). - TypeScript adds static types. Node 24+ can run
.tsfiles directly by stripping the types, and plain JS files can get type checking through JSDoc comments plus// @ts-check.
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
| Tool | Used for | Where |
|---|---|---|
| Node REPL | quick experiments | REPL blocks in every chapter |
node file.js, -e/-p | running examples | every chapter |
"type": "module" | ES modules by default | Ch. 1, 14, playground |
| npm scripts | npm start / npm test | Ch. 16 |
node:test + assert/strict | tests | Ch. 16 |
process.argv/exitCode, parseArgs | command-line tools | Ch. 14, 16 |
fs/promises, timers/promises, readline | async I/O | Ch. 12, 13, 16 |
--inspect, DevTools | debugging | any 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.