Skip to content
Laviyx Learn

Try

Node.js and Express course menu

Node.js File System (fs): Read, Write and Manage Files

Work with files in Node.js using fs/promises: reading and writing text and JSON files, appending, checking whether files exist, handling ENOENT errors, creating and listing folders, file information with stat, and why to avoid synchronous methods in servers.

Beginner8 min readUpdated
On this page

The fs (file system) module reads and writes files and folders. Use the promise version, node:fs/promises, with async/await: it does not block the program while the disk works.

Write and read a text file#

notes.js (JavaScript)
import { readFile, writeFile } from "node:fs/promises";

await writeFile("notes.txt", "Buy milk\nCall the bank\n");
const text = await readFile("notes.txt", "utf8");

console.log(text.trim().split("\n"));
console.log("Characters:", text.length);
Terminal
node notes.js
Output
[ 'Buy milk', 'Call the bank' ]
Characters: 23
  • writeFile creates the file, or replaces it if it exists.
  • Pass "utf8" to readFile to get a string; without an encoding you get a Buffer of raw bytes.
  • Top-level await works in ES modules.

Append to a file#

log.js (JavaScript)
import { appendFile, readFile } from "node:fs/promises";

for (const event of ["server started", "user signed in", "order placed"]) {
  await appendFile("app.log", `${event}\n`);
}
console.log(await readFile("app.log", "utf8"));
Terminal
node log.js
Output
server started
user signed in
order placed

JSON files#

JSON files are a simple way to store small amounts of data: parse after reading, stringify before writing.

products.json (JSON)
[
  { "id": 1, "name": "Notebook", "price": 60, "stock": 120 },
  { "id": 2, "name": "Gel pen", "price": 25, "stock": 0 },
  { "id": 3, "name": "Backpack", "price": 1299, "stock": 8 }
]
restock.js (JavaScript)
import { readFile, writeFile } from "node:fs/promises";

const products = JSON.parse(await readFile("products.json", "utf8"));
const outOfStock = products.filter((p) => p.stock === 0).map((p) => p.name);
console.log("Out of stock:", outOfStock);

const updated = products.map((p) => (p.stock === 0 ? { ...p, stock: 50 } : p));
await writeFile("products.json", JSON.stringify(updated, null, 2));
console.log("Saved. Gel pen stock is now", updated[1].stock);
Terminal
node restock.js
Output
Out of stock: [ 'Gel pen' ]
Saved. Gel pen stock is now 50

JSON.stringify(data, null, 2) writes indented, readable JSON. For anything bigger than a small config or demo, use a database: a JSON file is rewritten completely on every save and can be corrupted if two requests write at once.

Handling missing files#

Reading a file that does not exist throws an error with code: "ENOENT" ("error, no entry"). Catch it and decide what to do:

settings.js (JavaScript)
import { readFile } from "node:fs/promises";

async function loadSettings() {
  try {
    return JSON.parse(await readFile("settings.json", "utf8"));
  } catch (err) {
    if (err.code === "ENOENT") return { theme: "light", language: "en" };   // defaults
    throw err;                                                             // other errors are real problems
  }
}

console.log(await loadSettings());
Terminal
node settings.js
Output
{ theme: 'light', language: 'en' }

Common error codes: ENOENT (not found), EACCES (permission denied), EEXIST (already exists), EISDIR (expected a file, found a folder). Prefer trying the operation and handling the error over checking first with access: the file could change between the check and the use.

Folders#

folders.js (JavaScript)
import { mkdir, readdir, rm, stat, writeFile } from "node:fs/promises";

await mkdir("reports/2026/october", { recursive: true });       // creates parent folders too
await writeFile("reports/2026/october/sales.csv", "day,amount\n1,4500\n2,5200\n");
await writeFile("reports/2026/summary.txt", "October: 9700");

console.log(await readdir("reports/2026"));
const info = await stat("reports/2026/october/sales.csv");
console.log("Size in bytes:", info.size, "| is a file:", info.isFile());

const entries = await readdir("reports", { recursive: true });
console.log(entries.length, "entries inside reports");

await rm("reports", { recursive: true, force: true });           // delete the folder and its contents
console.log("Deleted reports/");
Terminal
node folders.js
Output
[ 'october', 'summary.txt' ]
Size in bytes: 25 | is a file: true
4 entries inside reports
Deleted reports/

readdir lists names (order is not guaranteed on every system, so sort if it matters). rm with recursive: true deletes a folder and everything in it: use it with care.

Synchronous and callback versions#

fs also has synchronous methods (readFileSync) and callback-style ones (fs.readFile(path, callback)):

JavaScript
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("config.json", "utf8"));

Synchronous calls block the whole program until the disk responds. That is fine in short scripts and at start-up (reading config once), but in a server it stops every other request from being handled. Use fs/promises in request handlers. Callback APIs appear in older code; promises with await are easier to read.

Common mistakes#

Practice#

Practice exercise

Write todo.js that keeps tasks in tasks.json: if the file does not exist, start with an empty list; add the task "Water the plants"; save the file; and print how many tasks are saved. Running it twice should report 1 and then 2.

Show hint

Use the ENOENT pattern to default to [], push the new task, then writeFile with JSON.stringify(tasks, null, 2).

Show solution
todo.js (JavaScript)
import { readFile, writeFile } from "node:fs/promises";

async function loadTasks() {
  try {
    return JSON.parse(await readFile("tasks.json", "utf8"));
  } catch (err) {
    if (err.code === "ENOENT") return [];
    throw err;
  }
}

const tasks = await loadTasks();
tasks.push({ text: "Water the plants", done: false });
await writeFile("tasks.json", JSON.stringify(tasks, null, 2));
console.log(`${tasks.length} task(s) saved`);
Terminal
node todo.js
node todo.js
Output
1 task(s) saved
2 task(s) saved

Key takeaway#

Next: Paths and URLs.

Finished this lesson?