JS Sketchbooksee JavaScript think ✏️
← back to phase 9

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.”

watch it happen
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”s

Why 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 project folder, on the real diskshop-tests/└ package.json└ tests/└ report.jsthe first browser-impossible power: real files. Webpages are sandboxed awayfrom your disk — Node isn’t
under the hood

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.

your turn

⌨️ 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). Print joinPath("reports", "day1", "report.txt").
  • Model the disk: const disk = {}. Write writeFile(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:

reports/day1/report.txt
suite: 12 passed, 1 failed

✏️ 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?

teach it back

🗣️ 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.

a few sentences, minimum — you’ve got this
to remember
Node touches the real disk (browsers are sandboxed away from it): writeFileSync(path, content) creates/overwrites; readFileSync(path, "utf8") reads text. Sync = BLOCKING; fs/promises + await is the non-blocking flavor.
fs and path ship with Node ("node:" = built-in, no install). Never glue paths — path.join picks / or \ per OS.
Two “here”s: process.cwd() = the terminal’s folder (relative paths resolve HERE) vs the script’s own home. Test artifacts (playwright-report/, test-results/) are written with these tools — and belong in .gitignore.