9.5 — The file system
Here it is — the first thing Node does that a browser is FORBIDDEN to do: touch real files on the real disk. (Imagine any webpage you visit reading your documents — that’s why pages are sandboxed. Node is a program on YOUR computer; files are its birthright.)
For a tester this is home turf: every run writes reports, screenshots, videos, traces. Today: write, read, build paths that survive Windows, and untangle the two meanings of “here.”
import { writeFileSync, readFileSync } from "node:fs";
import path from "node:path";
const line = "suite: 12 passed, 1 failed";
writeFileSync("report.txt", line);
const back = readFileSync("report.txt", "utf8");
console.log(back);
const full = path.join("reports", "day1", "report.txt");
console.log(full);
console.log(process.cwd()); // the terminal’s folder
// vs. the folder this FILE lives in — two different “here”sWhy browsers can’t: any webpage running with disk access could read your documents and mail them home — so the browser sandbox forbids it entirely. Node has no such wall: it’s a program YOU chose to run, like any installed app. Files are fair game.
The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”
The promise flavor looks like this: import { readFile } from "node:fs/promises" then const text = await readFile("report.txt", "utf8") — 6.6’s machinery verbatim, and the version servers use so the thread never stands in line. 9.6 shows what happens backstage while it waits.
The classic cwd-proof pattern used __dirname — “this file’s folder” — glued with path.join(__dirname, "data.json"). In ESM files (9.3!) __dirname doesn’t exist (it came from the CommonJS wrapper); the modern replacement is import.meta.dirname. Same idea: anchor to the FILE, not the terminal.
More of the fs toolbox, for recognition: mkdirSync(path, { recursive: true }) builds folder chains, existsSync answers “is it there?”, readdirSync lists a folder (ls, programmatically), rmSync deletes. Test setup and teardown are made of these.
Job note: “where did my screenshot go?” is a genuine daily question. The answer is always some path.join of an output folder, resolved against cwd — which is why CI configs run suites from the repo root, every time, on purpose.
⌨️ the report writer (on a model disk)
The sandbox has no real disk, so model it: a joiner that assembles paths safely, and a disk object that files can be written to and read from.
requirements:
- Write
joinPath(...parts)— rest parameters (3.10) gather any number of segments; return them joined with"/"(arrays know how). PrintjoinPath("reports", "day1", "report.txt"). - Model the disk:
const disk = {}. WritewriteFile(disk, path, content)storing the content under that path — the path arrives as a string variable, so choose the property syntax accordingly (4.4). - Write
readFile(disk, path)returning what’s stored. Write the line"suite: 12 passed, 1 failed"to the joined path, read it back, print it.
when you press RUN, the console must show exactly:
✏️ Quick check 1
readFileSync("report.txt") without "utf8" as the second argument hands you not text but raw ___? (one word)
✏️ Quick check 2
Why path.join("a", "b") instead of "a" + "/" + "b" — which operating system uses a DIFFERENT path separator?
✏️ Quick check 3
A script does readFileSync("data.json"). You run it from a different folder than the script lives in. Does "data.json" resolve against the terminal’s folder (cwd) or the script’s folder?
🗣️ Now teach it back
Explain to a friend: why Node can touch files when browsers can’t, what the Sync suffix means, why path.join beats gluing strings, and the difference between cwd and the script’s own folder.
Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.