Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Node.js File System fs Module Read Write: Tutorial, Examples, FAQs & Interview Tips

File System Module in Node.js

The fs module is a built-in Node.js module used to work with the file system of your computer or server. It allows a Node.js application to read files, write files, append content, rename files, delete files, inspect directories, and perform many other file-related operations. Since many real applications store logs, read templates, handle uploads, export reports, or load configuration files, the file system module is one of the most practical modules to learn early in Node.js.

The file system module provides both asynchronous and synchronous methods. Asynchronous methods are generally preferred in production because they do not block the main event loop while waiting for disk operations. Synchronous methods are sometimes useful in simple scripts, quick setup tasks, or small utilities where blocking behavior is acceptable and code simplicity matters more than concurrency.

Importing the File System Module

The file system module is built into Node.js, so no npm installation is required.

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

Modern Node.js code often also uses the promise-based API for cleaner async/await syntax.

Import Promise API
const fs = require("fs/promises");

Reading a File Asynchronously

Asynchronous file reading is usually the better default in Node.js applications. The program starts the file read and continues running while the operating system handles the I/O work. When the file has been read, the callback receives either an error or the file contents.

Async Read File
const fs = require("fs");

fs.readFile("file.txt", "utf8", (error, data) => {
    if (error) {
        console.error("Read failed:", error.message);
        return;
    }

    console.log(data);
});

Notice the use of "utf8". Without an encoding, Node.js returns a buffer instead of a text string. If you want to read a plain text file, specifying the encoding makes the output easier to work with.

Reading a File Synchronously

A synchronous file read blocks execution until the operation finishes. This means the rest of the program waits. While that is not ideal for most servers, it can still be fine in tiny scripts, setup tasks, or command-line tools.

Sync Read File
const fs = require("fs");

const data = fs.readFileSync("file.txt", "utf8");
console.log(data);

If you are building a web server or API, prefer asynchronous methods so the application can continue handling other requests while file operations are in progress.

Writing a File

fs.writeFile() creates a new file if it does not exist, or overwrites the existing file if it does exist. This is important to remember, because accidental overwriting is a common beginner mistake.

Async Write File
const fs = require("fs");
const content = "Node.js can write to files.";

fs.writeFile("file.txt", content, error => {
    if (error) {
        console.error("Write failed:", error.message);
        return;
    }

    console.log("Content written successfully!");
});
Sync Write File
const fs = require("fs");
const content = "Node.js can write to files.";

fs.writeFileSync("my_file.txt", content);
console.log("Content written successfully!");

Appending to a File

If you want to add new content to the end of a file instead of replacing the existing content, use appendFile(). This is useful for logging, activity history, report generation, and audit trails.

Append File
const fs = require("fs");

fs.appendFile("log.txt", "New log entry\n", error => {
    if (error) {
        console.error("Append failed:", error.message);
        return;
    }

    console.log("Log entry added");
});

Opening a File

Opening a file is a lower-level operation where Node.js returns a file descriptor. In many simple use cases, developers read or write files directly without calling open() first. Still, it is useful to understand that file descriptors exist, especially when working with advanced file operations or streams.

Open File
const fs = require("fs");

fs.open("file.txt", "r", (error, fd) => {
    if (error) {
        console.error("Open failed:", error.message);
        return;
    }

    console.log("File opened. Descriptor:", fd);
});

The second argument, "r", means the file is opened for reading. Other flags such as "w" and "a" are used for writing and appending.

Deleting a File

To remove a file, use unlink(). This is a destructive action, so your application should usually confirm that the target exists or that the delete request is valid before removing it.

Delete File
const fs = require("fs");

fs.unlink("old-file.txt", error => {
    if (error) {
        console.error("Delete failed:", error.message);
        return;
    }

    console.log("File deleted successfully");
});

Renaming a File

Renaming is another very common task, especially for uploads, backups, and report generation.

Rename File
const fs = require("fs");

fs.rename("draft.txt", "final.txt", error => {
    if (error) {
        console.error("Rename failed:", error.message);
        return;
    }

    console.log("File renamed successfully");
});

Working with Directories

The file system module can also create, read, and remove directories. This is useful for organizing uploads, logs, temporary files, or generated reports.

Directory Operations
const fs = require("fs");

fs.mkdir("reports", { recursive: true }, error => {
    if (error) {
        console.error("Create directory failed:", error.message);
        return;
    }

    console.log("Directory created");
});

fs.readdir(".", (error, files) => {
    if (error) {
        console.error("Read directory failed:", error.message);
        return;
    }

    console.log(files);
});

The option { recursive: true } allows nested folders to be created if needed. This makes directory setup easier in many applications.

Checking Whether a File Exists

Sometimes a program must check whether a file is present before reading, deleting, or replacing it. The promise-based access() method is commonly used for this kind of validation.

Check File Access
const fs = require("fs/promises");

async function checkFile() {
    try {
        await fs.access("file.txt");
        console.log("File exists");
    } catch (error) {
        console.log("File does not exist or cannot be accessed");
    }
}

checkFile();

Using fs/promises with async/await

Modern Node.js applications often use the promise-based API because it works naturally with async/await. This style avoids nested callbacks and usually reads more cleanly.

Promise-Based File Read
const fs = require("fs/promises");

async function loadContent() {
    try {
        const data = await fs.readFile("file.txt", "utf8");
        console.log(data);
    } catch (error) {
        console.error("Read failed:", error.message);
    }
}

loadContent();

This approach is now very common in production code because it keeps asynchronous file logic readable while still avoiding blocking behavior.

Reading Large Files with Streams

readFile() loads the entire file into memory. That is fine for small files, but not always ideal for very large files. In those cases, streams are usually a better option because they process data in chunks.

Read Stream Example
const fs = require("fs");

const stream = fs.createReadStream("big-file.txt", "utf8");

stream.on("data", chunk => {
    console.log("Received chunk:", chunk.length);
});

stream.on("end", () => {
    console.log("Finished reading file");
});

Streams are very important in Node.js for large files, file uploads, downloads, and efficient data transfer. They keep memory usage lower compared with reading everything at once.

Using path.join() with File Operations

The file system module is often used together with the path module. This combination helps build reliable file paths relative to the current script rather than depending on where the terminal was opened.

Reliable File Path Example
const fs = require("fs");
const path = require("path");

const filePath = path.join(__dirname, "data", "users.json");

fs.readFile(filePath, "utf8", (error, data) => {
    if (error) {
        console.error(error.message);
        return;
    }

    console.log(data);
});

Common Beginner Mistakes

A common mistake is forgetting to specify an encoding when reading a text file and then being confused when a buffer is returned. Another is using synchronous file methods in a server application where blocking behavior can slow down request handling. Beginners also often overwrite files accidentally with writeFile() when they really meant to append. The original short example on this page also had a variable mismatch bug between error and err, which is exactly the kind of small detail that can break file code quickly.

Another frequent issue is using relative file names without considering the current working directory. If the app is started from a different folder, paths like "file.txt" may fail unexpectedly. Combining __dirname with path.join() usually makes filesystem code more reliable.

A Practical Mental Model

Think of the file system module as Node.js's bridge to files and folders on disk. If your program needs to persist logs, export reports, read configuration, manage uploads, or inspect the contents of a folder, the fs module is the standard tool for the job. Once you are comfortable with reading, writing, appending, deleting, and path handling, a large part of everyday Node.js backend work becomes much easier.

Key Takeaways
  • The fs module is built into Node.js and supports reading, writing, appending, renaming, deleting, and inspecting files.
  • Prefer asynchronous methods in production so file I/O does not block the event loop.
  • Use fs/promises for cleaner async/await-based code.
  • writeFile() overwrites existing content, while appendFile() adds to the end.
  • Use streams like createReadStream() for large files instead of loading everything into memory.
  • Combine fs with path.join(__dirname, ...) for reliable project-relative paths.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.