Skip to Content
πŸŽ‰ Welcome to my notes πŸŽ‰
Node.js2. FS Module

File System Module - Handling Basic file Operations

πŸ“ fs module in Node.js

The fs module in Node.js is a built-in library that provides an API for interacting with the file system. It enables you to perform a wide range of file operations, such as: reading, writing, deleting, and manipulating files and directories. The fs module is essential for building applications that require file handling, such as web servers, command-line tools, and data processing scripts.

This module provides both synchronous and asynchronous methods for interacting with the file system, which is crucial for tasks like reading files, writing data, watching for changes, and organizing directory structures.

From a performance and architectural perspective, Node.js handles file operations asynchronously, but also gives you the option to work synchronously where necessary. The fs module provides traditional callback-based APIs, as well as modern Promise-based alternatives through the fs.promises namespace introduced in newer Node.js versions.

🚨 fs module provides an object with various methods.

β€œThe filesystem is the foundation of any backend application. Knowing how to interact with it properly opens doors to automation, security, and performance.” β€” Unknown Node.js Architect

πŸ“¦ Import fs module

// CommonJS syntax const fs = require("fs"); // or const fs = require("fs/promises"); // ES6 syntax import fs from "fs"; // or import fs from "fs/promises";

πŸ“‚ Understanding File Reading in Node.js

The fs module provides several methods for reading files, both synchronously and asynchronously. The most commonly used methods are fs.readFile() for asynchronous reading and fs.readFileSync() for synchronous reading.

βœ… Using fs.readFile() – Asynchronous

The fs.readFile() method reads the entire contents of a file asynchronously. This is a non-blocking call, and it’s highly recommended for most real-world applications where performance and scalability matter.

readFile.js
const fs = require("fs"); fs.readFile("example.txt", "utf-8", (err, data) => { if (err) { console.error("Error reading file:", err); return; } console.log("File content:", data); });
  • Parameters:

    • path: The path to the file you want to read.
    • encoding: The character encoding to use (e.g., β€œutf-8”). If not specified, the result will be a Buffer object.
    • callback: A callback function that takes two arguments: an error object (if any) and the file data.
  • Returns: If no encoding is specified, the data returned is a Buffer.

❌ Using fs.readFileSync() – Synchronous

The synchronous counterpart, fs.readFileSync(), blocks the event loop until the file is completely read. This is useful in scripts where execution order matters, such as during server initialization.

readFileSync.js
const fs = require("fs"); try { const data = fs.readFileSync("example.txt", "utf-8"); console.log("File content:", data); } catch (err) { console.error("Error:", err); }
  • Parameters:

    • path: The path to the file you want to read.
    • encoding: The character encoding to use (e.g., β€œutf-8”). If not specified, the result will be a Buffer object.
    • options: An optional object that can include flags and other options.
  • Returns: If no encoding is specified, the data returned is a Buffer.

✨ Using fs.promises.readFile() – Modern Async

Modern Node.js development encourages using Promises and async/await. With fs.promises.readFile(), you can avoid callback hell and write clean asynchronous code.

readFilePromise.js
const fs = require("fs").promises; async function readFile() { try { const data = await fs.readFile("example.txt", "utf-8"); console.log("File content:", data); } catch (err) { console.error("Error reading file:", err); } } readFile();

🧱 Error Handling and File Not Found Cases

When using fs.readFile() or fs.readFileSync(), you should always handle errors, especially for cases where the file may not exist or the path is incorrect. The error object will contain information about what went wrong.

Some errors such as: File not found (ENOENT), Permission errors (EACCES), Incorrect file encoding.

errorHandling.js
fs.readFile("./imogu.jpg", "utf-8", (err, data) => { if (err) { if (err.code === "ENOENT") { console.error("File does not exist."); } else { console.error("An error occurred:", err); } return; } console.log("File content:\n\n ", data); });

πŸ›  Real-World Use Cases

  • Configuration Files: Reading JSON or YAML configuration files for application settings.
  • Data Processing: Reading CSV or text files for data analysis or transformation.
  • Log Files: Reading and processing log files for monitoring or debugging purposes.
  • Markdown Files: Reading Markdown files for documentation or content management systems.

✍️ Writing and Appending to Files Using fs

Writing to files is just as essential as reading from them in any backend or CLI application. Whether you are saving logs, generating reports, updating configurations, or storing temporary data, the fs module in Node.js provides powerful and flexible methods for writing to files.

The fs module provides several methods for writing to files, both synchronously and asynchronously. The most commonly used methods are fs.writeFile() for asynchronous writing and fs.writeFileSync() for synchronous writing.
The fs.appendFile() method is used to append data to a file, while fs.appendFileSync() is its synchronous counterpart.

πŸ“ Using fs.writeFile() – Asynchronous File Writing

The fs.writeFile() method allows you to create or overwrite a file asynchronously. If the file does not exist, it will be created. If it already exists, its content will be replaced.

writeFile.js
const fs = require("fs"); fs.writeFile("output.txt", "Hello, Node.js!", "utf-8", (err) => { if (err) { console.error("Error writing file:", err); } else { console.log("File has been written successfully!"); } });
  • Parameters:

    • path – The path to the file you want to read.
    • data – The data you want to write (string or Buffer).
    • encoding – Optional (default: β€˜utf-8’).
    • callback – Called when writing completes or fails.

πŸ”’ Using fs.writeFileSync() – Synchronous File Writing

writeFileSync.js
const fs = require("fs"); try { fs.writeFileSync("output.txt", "This is synchronous content.", "utf-8"); console.log("File written successfully (sync)."); } catch (err) { console.error("Error writing file:", err); }

⚑ Using fs.promises.writeFile() – Modern Async Writing

To write files using async/await, Node.js provides a promise-based method:

writeFilePromise.js
const fs = require("fs").promises; async function writeContent() { try { await fs.writeFile("async-file.txt", "Async/Await rocks!", "utf-8"); console.log("Async file written successfully."); } catch (err) { console.error("Failed to write file:", err); } } writeContent();

βž• Appending Data with fs.appendFile()

If you want to add content to an existing file without overwriting it, use fs.appendFile(). This method appends data to the end of the file. If the file does not exist, it will be created.

appendFile.js
const fs = require("fs"); fs.appendFile("log.txt", "\nNew log entry", "utf-8", (err) => { if (err) throw err; console.log("Log updated."); });

This method is perfect for logging, journaling, or progressive file creation tasks.

πŸ•’ Appending Data with fs.appendFileSync()

appendFileSync.js
const fs = require("fs"); try { fs.appendFileSync("log.txt", "\nSync log entry"); console.log("Log updated (sync)."); } catch (err) { console.error("Error:", err); }

πŸ“ Appending Data with fs.promises.appendFile()

appendFilePromise.js
const fs = require("fs").promises; async function appendLog() { try { await fs.appendFile("log.txt", "\nAnother async entry"); console.log("Log updated (promise)."); } catch (err) { console.error("Failed to append:", err); } } appendLog();

Appending files with promises gives you clean, composable code. It’s great when combining multiple write operations.

πŸ›  Real-World Use Cases

  • Logging: Appending log entries to a log file for monitoring and debugging.
  • Configuration Updates: Updating configuration files without losing existing settings.
  • Report Generation: Write tabular reports dynamically.
  • Templating Engines: Write files post-render.

πŸ“‚ Working with Directories Using fs

Managing directories is a key part of working with any file system. In Node.js, the fs module offers various methods for creating, removing, renaming, and listing directories. These operations are essential when building tools like static site generators, file explorers, log managers, or automation scripts.

β€œA directory is not just a container of filesβ€”it’s a logical structure to organize your data.” – Unknown Node.js Architect

The fs module provides several methods for working with directories, both synchronously and asynchronously. The most commonly used methods are fs.mkdir() for creating directories, fs.rmdir() for removing directories, and fs.readdir() for listing directory contents. The fs.promises API also provides promise-based versions of these methods for cleaner async/await syntax.

πŸ“ Creating Directories Using fs.mkdir() – Asynchronous Creation

createFolder.js
const fs = require("fs"); fs.mkdir("myFolder", (err) => { if (err) { return console.error("Directory creation failed:", err); } console.log("Directory created."); });

By default, mkdir will throw an error if the directory already exists. Use the { recursive: true } option to avoid this and ensure parent directories are created as needed.

createFolder.js
fs.mkdir("logs/errors/2025", { recursive: true }, (err) => { if (err) throw err; console.log("Nested directories created."); });

πŸ“ Creating Directories Using fs.mkdirSync() – Synchronous Creation

createFolderSync.js
const fs = require("fs"); try { fs.mkdirSync("syncFolder", { recursive: true }); console.log("Synchronous folder created."); } catch (err) { console.error(err); }

πŸ“ Creating Directories Using fs.promises.mkdir() – Modern Async Creation

createFolderPromise.js
const fs = require("fs").promises; async function createFolder() { try { await fs.mkdir("asyncFolder", { recursive: true }); console.log("Async folder created."); } catch (err) { console.error("Async mkdir failed:", err); } } createFolder();

πŸ—‘οΈ Removing Directories Using fs.rmdir() – Asynchronous Removal

This method removes a directory, but it must be empty.

deleteEmptyFolder.js
const fs = require("fs"); fs.rmdir("myFolder", (err) => { if (err) { return console.error("Failed to remove folder:", err); } console.log("Folder removed."); });

πŸ—‘οΈ Removing Directories Using fs.rmdirSync() – Synchronous Removal

deleteEmptyFolderSync.js
const fs = require("fs"); try { fs.rmdirSync("syncFolder"); console.log("Synchronous folder removed."); } catch (err) { console.error(err); }

πŸ—‘οΈ Removing Directories Using fs.promises.rmdir() – Modern Async Removal

deleteEmptyFolderPromise.js
const fs = require("fs").promises; async function removeFolder() { try { await fs.rmdir("asyncFolder"); console.log("Async folder removed."); } catch (err) { console.error("Async rmdir failed:", err); } } removeFolder();

⚠️ Removing Non-Empty Directories

To remove a directory along with all its contents (files and subfolders), use fs.rm() or fs.rmSync() with the { recursive: true } option.

deleteFolder.js
const fs = require("fs"); fs.rm("myFolder", { recursive: true, force: true }, (err) => { if (err) { return console.error("Failed to remove folder:", err); } console.log("Folder and its contents removed."); });
deleteFolderPromise.js
const fs = require("fs").promises; async function removeFolder() { try { await fs.rm("myFolder", { recursive: true, force: true }); console.log("Async folder and its contents removed."); } catch (err) { console.error("Async rm failed:", err); } } removeFolder();

πŸ“‚ Listing Directory Contents Using fs.readdir() – Asynchronous Listing

The fs.readdir() method reads the contents of a directory asynchronously. It returns an array of filenames in the directory.

listFiles.js
const fs = require("fs"); fs.readdir(".", (err, files) => { if (err) { return console.error("Failed to read directory:", err); } console.log("Files:", files); });

You can pass { withFileTypes: true } to get Dirent objects, allowing inspection of file types.

listFiles.js (shows file type)
const fs = require("fs"); fs.readdir(".", { withFileTypes: true }, (err, files) => { files.forEach((file) => { console.log(file.name, file.isDirectory() ? "(dir)" : "(file)"); }); });

πŸ“‚ Listing Directory Contents Using fs.readdirSync() – Synchronous Listing

listFilesSync.js
const fs = require("fs"); try { const files = fs.readdirSync("."); console.log("Synchronous read:", files); } catch (err) { console.error(err); }

πŸ“‚ Listing Directory Contents Using fs.promises.readdir() – Modern Async Listing

listFilesPromise.js
const fs = require("fs").promises; async function listFiles() { try { const files = await fs.readdir("."); console.log("Async directory contents:", files); } catch (err) { console.error(err); } } listFiles();

πŸ›  Real-World Use Cases

  • File Management: Creating and organizing directories for file storage.
  • Log Management: Creating log directories for different log levels (info, error, debug).
  • Static Site Generators: Creating directories for generated HTML files.
  • Backup Scripts: Creating backup directories for storing copies of files.

πŸ” Inspecting File and Directory Metadata with fs.stat() and fs.lstat()

The fs module in Node.js provides methods to inspect file and directory metadata, such as size, permissions, timestamps, and more. This is crucial for tasks like file validation, monitoring changes, and managing resources efficiently. The two primary methods for inspecting metadata are fs.stat() and fs.lstat(). Both methods return a fs.Stats object that contains various properties about the file or directory.

  • fs.stat(): Retrieves metadata for a file or directory. It follows symbolic links  and provides information about the target file or directory.
  • fs.lstat(): Retrieves metadata for a file or directory without following symbolic links . It provides information about the symbolic link itself.

πŸ” Using fs.stat() – Asynchronous Metadata Inspection

stat.js
const fs = require("fs"); fs.stat("example.txt", (err, stats) => { if (err) { return console.error("Stat failed:", err); } console.log("Is file:", stats.isFile()); console.log("Is directory:", stats.isDirectory()); console.log("Size:", stats.size); console.log("Last modified:", stats.mtime); });

πŸ” Using fs.statSync() – Synchronous Metadata Inspection

statSync.js
const fs = require("fs"); try { const stats = fs.statSync("example.txt"); console.log("Is file:", stats.isFile()); console.log("Size:", stats.size); } catch (err) { console.error("Sync stat failed:", err); }

πŸ”Ž Using fs.stat() – Modern Async Metadata Inspection

statPromise.js
const fsPromises = require("fs").promises; async function statExample() { try { const stats = await fsPromises.stat("example.txt"); console.log("Created at:", stats.birthtime); console.log("Is directory:", stats.isDirectory()); } catch (err) { console.error("Promise stat failed:", err); } } statExample();

πŸ”— Using fs.lstat() – Asynchronous Metadata Inspection

lstat.js
const fs = require("fs"); fs.lstat("link-to-file", (err, stats) => { if (err) throw err; console.log("Is symbolic link:", stats.isSymbolicLink()); });

πŸ”— Using fs.lstatSync() – Synchronous Metadata Inspection

lstatSync.js
const fs = require("fs"); try { const stats = fs.lstatSync("link-to-file"); console.log("Is symbolic link:", stats.isSymbolicLink()); } catch (err) { console.error("Sync lstat failed:", err); }

πŸ”— Using fs.promises.lstat() – Modern Async Metadata Inspection

lstatPromise.js
const fs = require("fs").promises; async function lstatExample() { try { const stats = await fs.lstat("link-to-file"); console.log("Is symbolic link:", stats.isSymbolicLink()); } catch (err) { console.error("Promise lstat failed:", err); } } lstatExample();

βš–οΈ Comparing Files and Directories

You can compare files and directories using the fs.Stats object returned by fs.stat() or fs.lstat(). The fs.Stats object provides methods like isFile(), isDirectory(), and properties like size, birthtime, and mtime to help you determine the type and state of a file or directory.

One powerful use case for fs.stat() is to compare two files or directories. This is common in diff tools, synchronization tools, or migration scripts.

compareFiles.js
const fsPromises = require("fs").promises; async function compareFiles(file1, file2) { const [stat1, stat2] = await Promise.all([ fsPromises.stat(file1), fsPromises.stat(file2), ]); console.log("Sizes:", stat1.size, "vs", stat2.size); console.log("Modified:", stat1.mtime, "vs", stat2.mtime); } compareFiles("a.txt", "b.txt");

🚧 Edge Cases and Error Handling

  • Missing file or permission issues: Both stat and lstat will throw if the path doesn’t exist or access is denied.
  • Symbolic links: Always use lstat if you want information about the link itself.
  • Performance: Synchronous versions block the main thread. Use async methods for high-performance applications.
const fs = require("fs"); fs.stat("nonexistent.file", (err, stats) => { if (err) { if (err.code === "ENOENT") { console.error("File does not exist."); } else { console.error("Unexpected error:", err); } } else { console.log(stats); } });

πŸ›  Real-World Use Cases

  • Backup tools: Skip unchanged files using mtime
  • Media organizers: Sort files by creation date or size
  • Installers: Validate directory structures exist and have correct permissions
  • Symlink managers: Detect broken or circular links using fs.lstat()

πŸ–‹οΈ Renaming and Moving Files and Directories with fs.rename()

Renaming and moving files are core file system operations in any environment. Whether you’re organizing documents, managing uploads, creating log rotation systems, or performing backups, the ability to rename or move a file is essential.


In Node.js, the fs.rename() method serves both purposes: it can rename a file or directory, and it can move it to a new location. This dual functionality makes it a versatile and powerful tool.

The fs.rename() method takes two parameters: the old path and the new path. If the paths are on the same drive, this action is instantaneous. Here’s how it works:

  • Asynchronous Renaming:
rename.js
const fs = require("fs"); fs.rename("oldName.txt", "newName.txt", (err) => { if (err) throw err; console.log("File successfully renamed!"); });
  • Synchronous Renaming:
renameSync.js
const fs = require("fs"); try { fs.renameSync("oldName.txt", "newName.txt"); console.log("File renamed synchronously."); } catch (err) { console.error("Rename failed:", err); }
  • Promise-Based Renaming:
renamePromise.js
const fs = require("fs").promises; async function renameFile() { try { await fs.rename("old.txt", "new.txt"); console.log("Renamed using Promises!"); } catch (err) { console.error("Error renaming:", err); } } renameFile();

🚫 Keep in mind for Handling Rename Errors

  • Destination exists: On some platforms, trying to rename to an existing filename will throw an error.
  • Cross-device rename: Moving files across devices using fs.rename() might fail with EXDEV errors.
  • Permission denied: Insufficient file system permissions will trigger errors.

🧠 Best Practices

  • Validate existence of both source and destination paths before executing a rename.
  • Use path.resolve() to avoid relative path bugs.
  • Handle errors gracefully to improve reliability in file operations. Use Promises or async/await for cleaner, non-blocking code.
safeRename.js
const fs = require("fs").promises; async function safeRename(oldPath, newPath) { try { await fs.access(oldPath); // Check source await fs.rename(oldPath, newPath); console.log("Successfully renamed!"); } catch (err) { console.error("Rename error:", err.message); } } safeRename("myfile.txt", "renamedfile.txt");

πŸ›  Real-World Scenarios

  • Automated log management: Rename logs like app.log to app-2025-04-11.log
  • Media organization: Move photos or videos into folders by year/month
  • Versioning: Rename old backups with a timestamp or version number
  • Installers: Move files from temp folders to final destinations after verification

πŸ—‘οΈ Deleting Files and Directories Safely with fs.unlink() and fs.rmdir()

Deleting files and directories is one of the most powerfulβ€”and potentially dangerousβ€”operations you can perform on a filesystem. In Node.js, removing these elements is handled by methods like fs.unlink() and fs.rmdir() (or fs.rm() in newer versions).

🧹 Deleting Files with fs.unlink()

This method is used to delete a file from the filesystem. It does not move the file to the system trashβ€”it permanently deletes it.

deleteFile.js
// Asynchronous deletion const fs = require("fs"); fs.unlink("deleteMe.txt", (err) => { if (err) throw err; console.log("File was deleted"); });
deleteFileSync.js
// Synchronous deletion const fs = require("fs"); try { fs.unlinkSync("deleteMe.txt"); console.log("File deleted synchronously"); } catch (err) { console.error("Failed to delete:", err.message); }
deleteFilePromise.js
// Promise-based deletion const fs = require("fs").promises; async function deleteFile() { try { await fs.unlink("deleteMe.txt"); console.log("File deleted using promises"); } catch (err) { console.error("Error deleting file:", err); } } deleteFile();
  • ⚠️ Common Errors with fs.unlink()

    • ENOENT: The file does not exist
    • EPERM: Insufficient permissions
    • EISDIR: Attempting to delete a directory with fs.unlink()

πŸ“ Deleting Empty Directories with fs.rmdir()

The fs.rmdir() method is used to remove a directory, but it only works on empty directories. Trying to remove a non-empty directory will throw an error.

deleteEmptyFolder.js
// Asynchronous deletion of an empty directory const fs = require("fs"); fs.rmdir("empty-folder", (err) => { if (err) throw err; console.log("Directory deleted"); });
deleteEmptyFolderSync.js
// Synchronous deletion of an empty directory const fs = require("fs"); try { fs.rmdirSync("empty-folder"); console.log("Directory deleted synchronously"); } catch (err) { console.error("Failed to delete directory:", err); }
deleteEmptyFolderPromise.js
// Promise-based deletion of an empty directory const fs = require("fs").promises; async function deleteDirectory() { try { await fs.rmdir("empty-folder"); console.log("Directory deleted using promises"); } catch (err) { console.error("Error deleting directory:", err); } } deleteDirectory();

πŸ—‘οΈ Deleting Non-Empty Directories with fs.rm()

The fs.rm() method is a more powerful alternative that can delete non-empty directories with the recursive option.

deleteFolder.js
// Asynchronous deletion of a non-empty directory const fs = require("fs"); fs.rm("folder-to-remove", { recursive: true, force: true }, (err) => { if (err) throw err; console.log("Folder and all contents deleted!"); });
deleteFolderSync.js
// Synchronous deletion of a non-empty directory const fs = require("fs"); try { fs.rmSync("folder-to-remove", { recursive: true, force: true }); console.log("Folder deleted synchronously"); } catch (err) { console.error("Failed to delete folder:", err); }
deleteFolderPromise.js
// Promise-based deletion of a non-empty directory const fs = require("fs").promises; async function deleteFolder(folder) { try { await fs.rm(folder, { recursive: true, force: true }); console.log(`${folder} removed successfully`); } catch (err) { console.error("Error removing folder:", err.message); } } deleteFolder("backup-temp");

πŸ›‘οΈ Safe Deletion Practices

  • Check if the file or directory exists using fs.existsSync() or fs.access()
  • Log deletion activity for auditing
  • Validate user permissions before attempting deletion
  • When building CLI or production apps, include confirmation prompts
safeDelete.js
// safe check before delition const fs = require("fs"); const filePath = "important.log"; if (fs.existsSync(filePath)) { fs.unlink(filePath, (err) => { if (err) throw err; console.log("File deleted safely"); }); } else { console.log("File does not exist"); }

πŸ‘οΈ Monitoring Filesystem Changes with fs.watch()

The fs.watch() method provides a way to watch for changes on a file or directory. It emits events such as 'change' or 'rename', depending on the operation performed.

  • change: Triggered when a file is modified.
  • rename: Triggered when a file is renamed, moved, or deleted.
watchFile.js
const fs = require("fs"); fs.watch("example.txt", (eventType, filename) => { console.log(`File event: ${eventType}`); if (filename) { console.log(`Filename affected: ${filename}`); } });

πŸ“‚ You can also watch directories to respond to any file additions, deletions, or modifications inside that directory.

watchFolder.js
const fs = require("fs"); fs.watch("./watched-folder", (eventType, filename) => { console.log(`Directory change: ${eventType} on ${filename}`); });

πŸ”§ fs.watch() accepts a second parameter as an options object:

watchFile.js
const fs = require("fs"); fs.watch( "example.txt", { persistent: true, recursive: false, encoding: "utf8" }, (eventType, filename) => { console.log(`File event: ${eventType}`); if (filename) { console.log(`Filename affected: ${filename}`); } } );
  • persistent: If set to true, the process will continue running until explicitly terminated. Default is true.
  • recursive: If set to true, it will watch all subdirectories. Default is false.
  • encoding: The encoding to use for the filename. Default is β€˜utf8’.

🧠 To ensure robustness, always handle errors and close watchers when they’re no longer needed.

watchFileError.js
const fs = require("fs"); const watcher = fs.watch("important.txt"); watcher.on("error", (err) => { console.error("Watch error:", err); }); setTimeout(() => { watcher.close(); console.log("Stopped watching file."); }, 6000); // Stop after 6 seconds

🚨 Due to limitations of fs.watch(), many developers prefer using the chokidar library, which provides a more stable and cross-platform solution.

chokidar.js
// Install first: npm install chokidar const chokidar = require("chokidar"); const watcher = chokidar.watch("project/", { ignored: /^\./, // Ignore dotfiles persistent: true, }); watcher .on("add", (path) => console.log(`File ${path} has been added`)) .on("change", (path) => console.log(`File ${path} has been changed`)) .on("unlink", (path) => console.log(`File ${path} has been removed`));

πŸ›  Real-World Use Cases

  • Live-Reloading Applications: Restart or refresh a server on file save
  • Monitoring Config Files: Automatically apply changes
  • Audit Logging: Track which files are added or removed
  • CI/CD Pipelines: Trigger builds when code changes
Last updated on