Skip to main content

Chapter 11 - Organizing Files (JavaScript)

Here's a JavaScript-flavoured version of the same concepts, with small JS examples for each idea.


Organizing files (intro)

Chapter goal: automate copying, renaming, moving, deleting, and compressing many files/folders instead of doing it manually in a file explorer.

Example scenario: copy only all PDFs in a tree:

const fs = require("fs");
const path = require("path");
const os = require("os");

const root = path.join(os.homedir(), "project");
const dest = path.join(os.homedir(), "pdf_backup");
fs.mkdirSync(dest, { recursive: true });

function copyPdfs(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
copyPdfs(full);
} else if (entry.name.endsWith(".pdf")) {
fs.copyFileSync(full, path.join(dest, entry.name));
}
}
}

copyPdfs(root);

The fs module (like shutil)

Node.js fs provides high-level file operations: copy, move/rename, and delete.

Copying files and folders

  • fs.copyFileSync(src, dst) copies a single file.
  • fs.cpSync(src, dst, { recursive: true }) copies a whole directory tree (Node 16.7+).
const fs = require("fs");
const path = require("path");
const os = require("os");

const h = os.homedir();
fs.copyFileSync(
path.join(h, "spam", "file1.txt"),
path.join(h, "file1.txt")
);
fs.copyFileSync(
path.join(h, "spam", "file1.txt"),
path.join(h, "spam", "file2.txt")
);
fs.cpSync(path.join(h, "spam"), path.join(h, "spam_backup"), { recursive: true });

Moving and renaming files and folders

fs.renameSync(src, dst) moves and optionally renames a file/folder.

const fs = require("fs");
const path = require("path");
const os = require("os");

const h = os.homedir();
fs.mkdirSync(path.join(h, "spam2"), { recursive: true });

fs.renameSync(
path.join(h, "spam", "file1.txt"),
path.join(h, "spam2", "file1.txt")
);
fs.renameSync(
path.join(h, "spam", "file1.txt"),
path.join(h, "spam2", "new_name.txt")
);

Permanently deleting files and folders

  • fs.unlinkSync(path) deletes a file.
  • fs.rmdirSync(path) deletes an empty folder.
  • fs.rmSync(path, { recursive: true }) deletes a folder tree.

Dry-run pattern:

const fs = require("fs");
const path = require("path");
const os = require("os");

for (const f of fs.readdirSync(os.homedir())) {
if (f.endsWith(".txt")) {
const full = path.join(os.homedir(), f);
// fs.unlinkSync(full); // real delete
console.log("Deleting", full);
}
}

Deleting to the recycle bin

Use the trash npm package to move to recycle bin instead of permanent delete.

const trash = require("trash");

await trash(["file1.txt"]);

Walking a directory tree

fs.readdirSync (like os.listdir / iterdir)

  • fs.readdirSync(path) returns an array of names.
  • With { withFileTypes: true } you get Dirent objects with .isFile() / .isDirectory().
const fs = require("fs");
const os = require("os");

console.log(fs.readdirSync(os.homedir()));

Recursive walk (like os.walk)

JavaScript has no built-in os.walk, but a recursive function with readdirSync does the same.

const fs = require("fs");
const path = require("path");
const os = require("os");

function walk(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const subfolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
const files = entries.filter((e) => e.isFile()).map((e) => e.name);

console.log("The current folder is", dir);
for (const sf of subfolders) console.log(" SUBFOLDER:", sf);
for (const f of files) console.log(" FILE:", f);

for (const sf of subfolders) {
walk(path.join(dir, sf));
}
}

walk(path.join(os.homedir(), "spam"));

Example: renaming all files to uppercase

Inside the walk, use fs.renameSync to rename each file.

const fs = require("fs");
const path = require("path");
const os = require("os");

function renameUpper(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
renameUpper(full);
} else {
fs.renameSync(full, path.join(dir, entry.name.toUpperCase()));
}
}
}

renameUpper(path.join(os.homedir(), "spam"));

Compressing files with adm-zip (like zipfile)

Node.js has no built-in zip module. The adm-zip package is popular for creating and reading ZIPs.

Creating and adding to ZIP files

const AdmZip = require("adm-zip");
const fs = require("fs");

fs.writeFileSync("file1.txt", "Hello".repeat(10000));

const zip = new AdmZip();
zip.addLocalFile("file1.txt");
zip.writeZip("example.zip");

Reading ZIP files

const AdmZip = require("adm-zip");

const zip = new AdmZip("example.zip");
const entries = zip.getEntries();
for (const entry of entries) {
console.log(entry.entryName);
console.log(entry.header.size, entry.header.compressedSize);
}

Extracting from ZIP files

  • extractAllTo(path) extracts everything.
  • extractEntryTo(name, path) extracts one member.
const AdmZip = require("adm-zip");

const zip = new AdmZip("example.zip");
zip.extractAllTo("./spam", true);
zip.extractEntryTo("file1.txt", "./some/new/folders");

Project: backup_to_zip (incrementing ZIP backups)

Program: backs up a folder into numbered ZIPs like spam_1.zip, spam_2.zip, etc.

Step 1: Figure out the ZIP file's name

const fs = require("fs");
const path = require("path");

function backupToZip(folder) {
const folderName = path.basename(folder);
let number = 1;
let zipFilename;
while (true) {
zipFilename = `${folderName}_${number}.zip`;
if (!fs.existsSync(zipFilename)) break;
number++;
}
// ...

Step 2: Create the new ZIP file

const AdmZip = require("adm-zip");

console.log(`Creating ${zipFilename}...`);
const zip = new AdmZip();

Step 3: Walk the directory tree and add files

  function addFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
console.log(`Adding files in folder ${dir}...`);
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
addFiles(full);
} else {
console.log(` Adding file ${entry.name}...`);
zip.addLocalFile(full);
}
}
}

addFiles(folder);
zip.writeZip(zipFilename);
console.log("Done.");
}

backupToZip(path.join(require("os").homedir(), "spam"));

Ideas for other programs

Suggested variations using recursive walk, zip, and filters:

  • Archive only files with certain extensions (e.g. .txt, .js).
  • Archive everything except certain extensions.
  • Only archive folders that are very large or recently modified.

Example filter idea:

const AdmZip = require("adm-zip");
const fs = require("fs");
const path = require("path");
const os = require("os");

const root = path.join(os.homedir(), "project");
const zip = new AdmZip();

function addJsFiles(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
addJsFiles(full);
} else if (entry.name.endsWith(".js")) {
zip.addLocalFile(full);
}
}
}

addJsFiles(root);
zip.writeZip("code_backup.zip");

Summary (chapter recap)

  • Use fs to copy, move, rename, delete safely (with dry runs).
  • Use recursive readdirSync with withFileTypes to operate on entire directory trees.
  • Use adm-zip to compress/extract ZIP archives, combining with file operations for backups and packaging.

Practice questions

The chapter ends with conceptual questions, for example:

  1. Difference between fs.copyFileSync() and fs.cpSync()?
  2. Which function renames files?
  3. Difference between trash delete and fs.rmSync delete?
  4. How do you create a zip in Node.js?

(Answers in short: single file vs whole tree; fs.renameSync; recycle-bin vs permanent delete; use adm-zip or similar package.)


Practice programs

Four suggested exercises (same as Python, adapted to Node.js):

  1. Selectively Copying – copy only certain extensions into a new folder.
  2. Deleting Unneeded Files – find and print files over a size threshold (e.g. 100MB).
  3. Renumbering Files – close gaps in numbered filenames.
  4. Converting Dates – rename files from MM-DD-YYYY to DD-MM-YYYY using recursive walk, regex, and fs.renameSync.

Example snippet for "Deleting Unneeded Files":

const fs = require("fs");
const path = require("path");
const os = require("os");

function findLargeFiles(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
findLargeFiles(full);
} else {
const stats = fs.statSync(full);
if (stats.size > 100 * 1024 * 1024) {
console.log(full, "is larger than 100MB");
}
}
}
}

findLargeFiles(os.homedir());

Overall idea of the chapter

The chapter's main message is: Node.js provides fs for copying, moving, renaming, and deleting files, recursive readdirSync for walking directory trees, and packages like adm-zip for zip archives. The dry-run pattern and trash-based deletion are equally important safety habits in JavaScript.