Skip to content

Node.js File System — Complete Guide to Reading, Writing, and Managing Files

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Node.js File System. We cover key concepts, practical examples, and best practices to help you master this topic.

The Node.js fs (file system) module provides APIs for interacting with the file system, enabling reading, writing, deleting, and monitoring files and directories.

What You'll Learn

By the end of this tutorial, you'll use the fs module for synchronous and async file operations, directory management, file stats, watching files, and the promises API.

Why File System Module Matters

Most server applications need to read configuration files, write logs, Process uploads, or manage data files. The fs module is essential for these operations in tools like DodaZIP's file conversion pipeline.

Real-World Use

A log processing service reads multiple log files, parses entries, writes summary reports, and monitors a directory for new log files to process automatically.

FS Module Learning Path

flowchart LR
  A[Path Module] --> B[File System]
  B --> C[Streams]
  C --> D[Buffers]
  D --> E[Express.js]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

Reading Files

The fs module provides three patterns: synchronous, callback, and promise-based.

import fs from "node:fs";
import fsPromises from "node:fs/promises";

// Synchronous (blocks the event loop)
const data = fs.readFileSync("config.json", "utf8");
console.log(data);

// Callback (non-blocking)
fs.readFile("config.json", "utf8", (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promise (recommended)
const config = await fsPromises.readFile("config.json", "utf8");
console.log(config);

Writing Files

import fsPromises from "node:fs/promises";

await fsPromises.writeFile("output.txt", "Hello, world!", "utf8");
console.log("File written");

await fsPromises.appendFile("log.txt", `[${new Date().toISOString()}] Event logged\n`);
console.log("Appended");

Directory Operations

import fsPromises from "node:fs/promises";

// Create directory
await fsPromises.mkdir("data/logs", { recursive: true });

// Read directory
const files = await fsPromises.readdir("./data");
console.log(files);  // ['logs', 'config.json']

// Remove directory
await fsPromises.rmdir("data/temp");

// Remove recursively
await fsPromises.rm("data/temp", { recursive: true, force: true });

File Statistics

import fsPromises from "node:fs/promises";

const stats = await fsPromises.stat("config.json");
console.log(stats.isFile());       // true
console.log(stats.isDirectory());  // false
console.log(stats.size);           // 1240 (bytes)
console.log(stats.mtime);          // Last modified date

Watching Files

import fs from "node:fs";

fs.watch("data/logs", (eventType, filename) => {
  console.log(`File ${filename} changed: ${eventType}`);
});

File Copy and Rename

import fsPromises from "node:fs/promises";

await fsPromises.copyFile("source.txt", "backup.txt");
await fsPromises.rename("temp.txt", "final.txt");

Common Mistakes

1. Using Sync Methods in Server Request Handlers

readFileSync blocks the event loop. In a server, this freezes all requests until the file is read. Always use async methods.

2. Not Checking File Existence

Always handle the error case or check existence before operations.

3. Forgetting to Handle Errors

An unhandled error crashes the process. Always wrap in try/catch or provide error callbacks.

4. Hardcoding File Paths

Use path.join with __dirname or process.cwd() for cross-platform compatibility.

5. Opening Too Many Files

File descriptors are limited. Stream large files instead of reading them entirely into memory.

Practice Questions

1. What is the difference between readFile and createReadStream?

readFile loads the entire file into memory. createReadStream processes data in chunks, suitable for large files.

2. Which fs API should you use in a production web server?

The promise-based API (fs/promises) offers clean async/await syntax and proper error handling without blocking.

3. How do you create nested directories safely?

Use fsPromises.mkdir(path, { recursive: true }). It creates all parent directories if they don't exist.

4. What does fs.watch do?

It monitors a file or directory for changes and fires a callback when modifications occur.

5. Challenge: Write a script that watches a directory and copies new files to a backup location.

import fs from "node:fs";
import fsPromises from "node:fs/promises";
import path from "node:path";
const watchDir = "./incoming";
const backupDir = "./backup";
await fsPromises.mkdir(backupDir, { recursive: true });
fs.watch(watchDir, async (event, filename) => {
  if (event === "rename") {
    const src = path.join(watchDir, filename);
    const dest = path.join(backupDir, filename);
    await fsPromises.copyFile(src, dest);
    console.log(`Backed up ${filename}`);
  }
});

FAQ

When should I use readFileSync?

Only during startup or in CLI scripts where blocking is acceptable. Never in server request handlers.

How do I check if a file exists?

Use fsPromises.access(filePath) or try/catch on stat. fs.existsSync exists but is deprecated for async code.

What is the file descriptor limit?

The OS limits open file descriptors (e.g., 1024 per process on Linux). Always close file handles.

Can I read a file partially?

Use createReadStream with start and end options, or use fs.open and fs.read with position and length.

How do I delete a non-empty directory?

Use fsPromises.rm(path, { recursive: true }). This deletes the directory and all contents.

Mini Project: Simple Logger

Build a logging module that appends timestamped entries to a log file with automatic rotation.

import fsPromises from "node:fs/promises";
import path from "node:path";
class Logger {
  constructor(logDir = "./logs") {
    this.logDir = logDir;
  }
  async log(level, message) {
    await fsPromises.mkdir(this.logDir, { recursive: true });
    const filePath = path.join(this.logDir, `${new Date().toISOString().split("T")[0]}.log`);
    const entry = `[${new Date().toISOString()}] [${level}] ${message}\n`;
    await fsPromises.appendFile(filePath, entry);
  }
}
const logger = new Logger();
await logger.log("INFO", "Server started on port 3000");
await logger.log("ERROR", "Connection refused");

What's Next

Node.js Streams Node.js Buffers Express.js

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro