Stop 02 · Node.js FundamentalsTopics · 14/18

File System (fs)

Part of the Backend Developer Roadmap

In short

Logs, uploads, config files, generated reports: backends touch the disk all the time. Node's fs module handles it all, as long as you pick the right flavour and build paths properly.

Node's built-in module for reading, writing and manipulating files on disk — both synchronous and asynchronous (Promise-based) variants.

The mascot clerk pulls a file from a steel almirah full of folders, with a register open on the desk.

Think of it like this

The college record room

The record room clerk can fetch a file, add a line to a register, open a new folder or list what's in an almirah. You just hand over a request slip. Node's fs module is that clerk for your server's disk, and fs/promises lets you wait for him politely.

Watch it happen

Writing a log file

Your script
fs/promises
Disk

mkdir logs, recursive

1/7Create the folder. recursive: true means no error if it already exists.

The big ideas

What you need to know

Three flavours

Callback (fs.readFile), sync (fs.readFileSync) and promise (fs/promises). Use promises in servers, sync only at startup.

Paths need care

Relative paths start from where you ran node, not from the file. Build them with import.meta.dirname and path.join.

Write vs append

writeFile replaces the whole file. appendFile adds to the end, which is what you want for logs.

Errors have codes

Check err.code: ENOENT means not found, EEXIST already exists, EACCES permission denied.

See it in code

Walk through the code

js
1import { mkdir, appendFile, readFile, readdir, stat } from "node:fs/promises";2import path from "node:path";3 4const logDir = path.join(import.meta.dirname, "logs");5const logFile = path.join(logDir, "app.log");6await mkdir(logDir, { recursive: true });7await appendFile(logFile, `${new Date().toISOString()} server started\n`);8 9console.log(await readdir(logDir));            // [ 'app.log' ]10console.log((await stat(logFile)).size, "bytes");11 12try {13  await readFile(path.join(logDir, "missing.log"), "utf8");14} catch (err) {15  if (err.code === "ENOENT") console.log("No such file, and that's fine");16}

1/4Build paths from the script's own folder, so they don't depend on where you ran node.

Save as fs-demo.mjs and run it twice: the log grows by one line each time. import.meta.dirname (Node 20.11+) keeps paths working whichever folder you run it from.

Save yourself the pain

Common mistakes

Oops

Using "./data.json" and getting ENOENT when the app is started from another folder.

Do this instead

Relative paths resolve from the current working directory. Build paths with import.meta.dirname and path.join.

Oops

Calling fs.existsSync before every read or write.

Do this instead

The file can change between the check and the use. Just try the operation and handle ENOENT or EEXIST in catch.

Your mission

Build a tiny logger

~20 min

Quick check

You run node scripts/report.mjs from the project root. The script reads "./template.html", which sits next to it in scripts/. It fails with ENOENT. Why?

Go deeper

  1. 1Read and write a file using both `fs.promises` and the callback-based API.
  2. 2Practice watching a directory for changes with `fs.watch`.
  3. 3Learn why you should almost always prefer the async `fs` methods on a server.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge