Chapter 7 of 18 · Node.js for Beginners — Complete Free Tutorial
The File System Module
Servers persist something — uploads, logs, settings, data. The Node.js for Beginners series now opens fs, Node's file system module: reading and writing text, appending and deleting, streaming large files without melting memory, and the JSON round-trip that chapter 16 will turn into a small database. Every technique here is the same one your favourite tools use — git, npm and your editor all store state in plain files.
Two flavours: sync and async
fs ships every operation in several styles. The pattern is uniform across the module.
| Style | Signature | Use when |
|---|---|---|
| Callback (async) | readFile(path, cb) | Legacy code — error-first callback |
| Promise (async) | readFile(path) from node:fs/promises | New code — pairs with async/await |
| Synchronous | readFileSync(path) | Startup config, scripts, tests — never in a server request |
Reading and writing files
import { writeFile, readFile } from 'node:fs/promises';
await writeFile('greeting.txt', 'Hello, files!\n', 'utf8');
await writeFile('greeting.txt', 'A second line\n', { flag: 'a' }); // append
const text = await readFile('greeting.txt', 'utf8');
console.log(text);
Expected output:
Hello, files!
A second line
- Always pass
'utf8'(or you get aBuffer— raw bytes that print as numbers). - The default flag
'w'truncates;'a'appends;'wx'fails if the file exists (handy for locks). - Missing file → the promise rejects with an error whose
codeis'ENOENT'. - For copying or moving, use the dedicated calls —
copyFileandrename— instead of reading and writing by hand; they are faster and handle partial writes correctly.
Working with directories and metadata
import { mkdir, readdir, stat, unlink } from 'node:fs/promises';
await mkdir('data', { recursive: true }); // no error if it exists
const entries = await readdir('data');
console.log('entries:', entries);
const info = await stat('greeting.txt');
console.log('bytes:', info.size, 'isFile:', info.isFile());
await unlink('greeting.txt'); // delete
console.log('deleted');
Expected output:
entries: [ 'todos.json' ]
bytes: 26 isFile: true
deleted
Every failure surfaces as a rejected promise with a machine-readable code: ENOENT (not found), EACCES (permissions), EISDIR (path is a directory). Branch on err.code, never on the message text.
Streams: large files without large memory
readFile loads the whole file into memory — fine for kilobytes, fatal for gigabytes. Streams process data in chunks as it flows.
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
await pipeline(
createReadStream('big-log.txt'),
createWriteStream('big-log-copy.txt'),
);
console.log('copied with constant memory');
Expected output: copied with constant memory, with RAM use flat regardless of file size. Streams also power HTTP request and response bodies in chapter 11 — the same chunk-by-chunk model. Reach for them as soon as a file might be larger than you are happy to hold in RAM, and prefer pipeline over manually piping: it propagates errors and closes the streams for you, which hand-rolled chains routinely forget to do.
Reading and writing JSON (a preview of chapter 16)
import { readFile, writeFile } from 'node:fs/promises';
async function loadJSON(path, fallback = []) {
try {
return JSON.parse(await readFile(path, 'utf8'));
} catch (err) {
if (err.code === 'ENOENT') return fallback;
throw err; // corrupt data must not vanish
}
}
async function saveJSON(path, data) {
await writeFile(path, JSON.stringify(data, null, 2) + '\n', 'utf8');
}
const todos = await loadJSON('data/todos.json');
await saveJSON('data/todos.json', [...todos, { id: 1, title: 'Learn fs', done: false }]);
console.log('stored', todos.length + 1, 'items');
Expected output: stored 1 items. Note the three rules baked in: a default for a missing file, a re-throw for unexpected errors, and pretty-printed JSON with a trailing newline so diffs stay readable.
Common mistakes
- Forgetting
'utf8'— you log a Buffer of byte numbers instead of text. readFileSyncin a request handler — blocks the event loop for every connected client.- Ignoring error codes — treating every failure as "file missing" hides permission and corruption bugs.
- Reading a huge file whole — use streams or
readlineonce files approach memory limits. - Write-then-crash loses data — overwrite patterns can truncate a file mid-write; write to a temp file and rename for critical data (chapter 16 does this).
Practice checklist
- Write a script that creates a folder and three text files, then lists them with sizes.
- Handle a deliberate
ENOENTand print a friendly message instead of a stack trace. - Copy a file with streams and compare memory use against
readFile/writeFile. - Build
loadJSON/saveJSONhelpers and round-trip an object of your own. - Append one line per run to a log file and open the result in an editor.
Files live in directories — which means paths, and paths deserve their own chapter. Next: Paths, Environment and Process.
Frequently Asked Questions
Node.js is a runtime that executes JavaScript outside the browser. It combines Google's V8 engine with APIs for files, networks, processes and streams, plus a package ecosystem (npm). You use it to build servers, command-line tools, APIs and anything that needs JavaScript running on a machine rather than in a page.
JavaScript is the language; Node.js is one environment that runs it. In the browser the language comes with a DOM, events and window APIs — in Node those are replaced by the file system, network and process modules. Syntax, functions, arrays, promises and async/await are identical, so your JavaScript knowledge transfers directly.
Browser code is sandboxed: it cannot read your files or open arbitrary ports. Node code has full machine access, which is why it is used for servers and tools but also why you never run untrusted code in it. There is no document or window in Node — the equivalents are global, process and modules.
npm is JavaScript's package manager and public registry. package.json describes your project: name, entry point, dependencies with version ranges, and scripts such as npm run dev. Installing a package copies it into node_modules and records the exact intent in package-lock.json so every machine installs identical versions.
Express is a minimal web framework for Node: a few thousand lines that give you routing, middleware, request parsing and static files on top of the raw HTTP server. It is the most common starting point for REST APIs and the backbone of this tutorial's project chapters.
Both work. CommonJS (require/module.exports) is the classic Node format; ES modules (import/export) are the standard and activated in a package.json with "type": "module". Modern projects favour ESM for browser compatibility, but you will read both in existing code, so this course teaches each.
Middleware are functions that run between the incoming request and the final response, in order: body parsing, logging, authentication, rate limits, error handling. Each receives the request, response and next function and can respond itself or pass control along — Express's pipeline model in one sentence.
A JSON file is perfect for learning CRUD: no setup, readable data, easy to reset. The moment you need concurrent writes, querying or multiple users, move to a real database — MySQL/PostgreSQL for relational data or MongoDB for documents. The storage chapter isolates persistence behind one module so swapping it later is straightforward.
A REST API exposes resources over HTTP using verbs on URLs: GET /api/todos lists, POST creates, PUT or PATCH updates, DELETE removes, and everything answers with JSON plus correct status codes (200, 201, 400, 404, 500). It is stateless — each request carries its own authentication — which makes it easy to cache, scale and consume from any frontend.
Node apps are long-running processes, not static files: deploy them to a Node-friendly host (a VPS, Render, Railway or Fly.io), keep secrets in environment variables, terminate HTTPS at a reverse proxy or platform edge, and send logs somewhere durable. The final chapter covers the full checklist including health checks and graceful shutdown.