Skip to content
Commitpedia
Log in Join & edit

Read and write files in Node.js with fs/promises

A complete Node script that walks a folder, reads text files, and writes a JSON report safely, showing the everyday fs/promises calls and how to handle their errors.

Not checked by a trusted editor yet Last edited Outdated?

Edit history of Read and write files in Node.js with fs/promises: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
javascript
Licence
CC0-1.0

What this changes

Reads every file under the folder (skipping node_modules) and writes a JSON report to report.json or the path you give, creating parent folders if needed. An existing file at that path is overwritten.

How to undo it

Delete the report, e.g. rm reports/src.json, and any folder it created, e.g. rmdir reports. If the output path already held a file, copy it first, because it is replaced.

Node’s node:fs/promises module gives you promise-based file functions that work naturally with await. This script uses the ones you will need most often: list a folder, read a file, get file info, create folders and write a file.

What the script does

  1. Lists every file under a folder, including subfolders, skipping node_modules.
  2. Records each file’s size and modification time, and counts lines for common text formats.
  3. Writes everything to a JSON report, using a temporary file and a rename so a crash never leaves a half-written report.
  4. Prints the five largest files.

The script

Save as dir-report.mjs. Requires Node 20.12 or later (for readdir with recursive and Dirent.parentPath).

#!/usr/bin/env node
// Usage: node dir-report.mjs <folder> [report.json]
import { readdir, readFile, stat, writeFile, rename, mkdir } from "node:fs/promises";
import path from "node:path";

const [folder = ".", output = "report.json"] = process.argv.slice(2);
const TEXT_EXTENSIONS = new Set([".js", ".mjs", ".ts", ".json", ".md", ".txt", ".css", ".html"]);

async function buildReport(root) {
  const entries = await readdir(root, { recursive: true, withFileTypes: true });
  const files = [];
  for (const entry of entries) {
    if (!entry.isFile()) continue;
    const fullPath = path.join(entry.parentPath, entry.name);
    if (fullPath.split(path.sep).includes("node_modules")) continue;
    const { size, mtime } = await stat(fullPath);
    const ext = path.extname(entry.name).toLowerCase();
    let lines = null;
    if (TEXT_EXTENSIONS.has(ext)) {
      const text = await readFile(fullPath, "utf8");
      lines = text === "" ? 0 : text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
    }
    files.push({ file: path.relative(root, fullPath), ext, size, lines, modified: mtime.toISOString() });
  }
  files.sort((a, b) => b.size - a.size);
  return {
    root: path.resolve(root),
    generatedAt: new Date().toISOString(),
    fileCount: files.length,
    totalBytes: files.reduce((sum, f) => sum + f.size, 0),
    files,
  };
}

async function writeJsonAtomic(file, data) {
  await mkdir(path.dirname(path.resolve(file)), { recursive: true });
  const tmp = `${file}.${process.pid}.tmp`;
  await writeFile(tmp, JSON.stringify(data, null, 2) + "\n", "utf8");
  await rename(tmp, file);
}

try {
  const report = await buildReport(folder);
  await writeJsonAtomic(output, report);
  console.log(`Wrote ${output}: ${report.fileCount} files, ${report.totalBytes} bytes`);
  for (const f of report.files.slice(0, 5)) {
    console.log(`  ${String(f.size).padStart(8)}  ${f.file}`);
  }
} catch (err) {
  if (err.code === "ENOENT") console.error(`Not found: ${err.path}`);
  else if (err.code === "EACCES") console.error(`Permission denied: ${err.path}`);
  else console.error(err);
  process.exitCode = 1;
}

Run it

node dir-report.mjs ./src reports/src.json
# Wrote reports/src.json: 42 files, 183220 bytes

The calls, one by one

  • readdir(dir, { recursive: true, withFileTypes: true }) returns Dirent objects so you can ask isFile() without an extra stat call.
  • readFile(path, "utf8") returns a string. Without the encoding you get a Buffer of raw bytes, which is what you want for images.
  • stat(path) returns size, timestamps and type information.
  • mkdir(dir, { recursive: true }) creates missing parent folders and does not fail if the folder already exists.
  • writeFile(path, data) creates or replaces a file. Use appendFile to add to the end instead.
  • rename(from, to) replaces the target in one step on the same file system, which is why it is used for the atomic write.

Handling errors

File functions reject with an Error that has a code property. Check that code instead of parsing the message:

  • ENOENT: the file or folder does not exist.
  • EACCES / EPERM: permission denied.
  • EISDIR: you tried to read a folder as a file.
  • EEXIST: the target exists, for example with writeFile(path, data, { flag: "wx" }).

To check whether a file exists, prefer simply trying to read it and handling ENOENT. A separate existence check can become wrong between the check and the read.

Tips

  • Build paths with path.join() and path.resolve(), never by gluing strings with /, so the script also works on Windows.
  • Relative paths are resolved against the current working directory, not the script’s folder. Use import.meta.dirname (Node 20.11+) for paths next to the script.
  • readFile loads the whole file into memory. For multi-gigabyte logs, use fs.createReadStream with node:readline to process line by line.

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

  • 0022 Build a small Node.js CLI with util.parseArgs A complete, dependency-free command-line tool in Node: options and flags with parseArgs, input from files or a pipe, help text, and meaningful… Intermediate · Step by step · Any system Script Node.js 15 Oct 2026
  • 0027 Fix: SyntaxError: Cannot use import statement outside a module The file uses ES module import syntax, but Node or the browser is loading it as a classic script or CommonJS. Tell… Intermediate · Fixes an error · Any system Article Common errors · Node.js 15 Oct 2026
  • 0263 Build a notes REST API with plain Node.js: CRUD, validation, status codes and CORS Write a small JSON API for notes using only Node.js built-ins: create, read, update and delete, input validation, correct HTTP status codes,… Intermediate · Guided project · Any system Article Making requests · Node.js · Status codes 15 Oct 2026
  • 0319 Fix: npm ERR! EACCES permission denied npm cannot write to a directory it needs: the global install location, its cache in ~/.npm, or a node_modules owned by root.… Beginner · Fixes an error · Linux, macOS Article Common errors · Node.js 15 Oct 2026
  • 0320 Fix: address already in use (EADDRINUSE) Something is already listening on the port your program wants. Find the process with lsof or ss, stop it or pick another… Beginner · Fixes an error · Linux, macOS Article Networking & firewalls · Node.js · Processes & ports 15 Oct 2026
  • 0386 Install Node.js on Linux, macOS and Windows Get Node.js and npm on any desktop OS. Use a version manager (nvm or fnm) when you work on several projects, or… Beginner · Step by step · Linux, macOS, Windows Article Installing languages & tools · Node.js 15 Oct 2026

Repos in the wild

  • Node.js nodejs/node The source of the Node.js runtime: a JavaScript engine (V8) plus APIs for files, networking, processes and more, used to run JavaScript outside the browser. GitHub
  • Express (expressjs/express) expressjs/express A minimal and flexible web framework for Node.js, built around routes and middleware, and the foundation of countless APIs and other frameworks. GitHub

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Commitpedia +4,766 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?