Comprehensive Guide to File System Operations in Node.js

Node.js provides the fs module to enteract with the file system in a manner similar to standard POSIX functions. This includes capabilities for opening, reading, writting, and closing files, as well as managing directories and file metadata. These operations are available in both synchronous and asynchronous forms.

Opening and Closing Files

To perform operations on a file, you first obtain a file descriptor. The synchronous approach blocks execution until the operation completes, while the asynchronous approach uses a callback.

const fs = require('fs');

// Synchronous open and close
try {
    const descriptor = fs.openSync('storage/example.log', 'w');
    console.log('File descriptor obtained:', descriptor);
    fs.closeSync(descriptor);
} catch (err) {
    console.error('Error in sync operation:', err);
}

// Asynchronous open and close
fs.open('storage/async_example.log', 'w', (err, fd) => {
    if (err) throw err;
    fs.close(fd, (closeErr) => {
        if (closeErr) throw closeErr;
        console.log('Asynchronous file closed successfully.');
    });
});

File Access Flags

The behavior of file operations is determined by the flags passed during the open call:

  • r: Open for reading. An exception occurs if the file does not exist.
  • r+: Open for reading and writing.
  • w: Open for writing. The file is created (if it does not exist) or truncated (if it exists).
    • w+: Open for reading and writing, creating or truncating the file as needed.
  • a: Open for appending. Data is written at the end of the file.
  • a+: Open for reading and appending.

Simplified Reading and Writing

For most common tasks, readFile and writeFile provide a high-level interface that handles opening and closing the file automatically.

const fs = require('fs');

const serverSettings = {
    port: 8080,
    host: 'localhost',
    debug: true
};

const content = JSON.stringify(serverSettings, null, 2);
const writeOptions = { encoding: 'utf8', flag: 'w' };

// Writing a file asynchronously
fs.writeFile('settings.json', content, writeOptions, (err) => {
    if (err) {
        console.error('Failed to save settings.');
    } else {
        console.log('Settings saved.');
        loadSettings();
    }
});

function loadSettings() {
    const readOptions = { encoding: 'utf8', flag: 'r' };
    fs.readFile('settings.json', readOptions, (err, data) => {
        if (err) {
            console.error('Failed to load settings.');
        } else {
            const parsed = JSON.parse(data);
            console.log('Port Configuration:', parsed.port);
        }
    });
}

Manual Synchronous Buffer Operations

When precise control over the buffer is required, readSync and writeSync allow you to specify offsets and lengths.

const fs = require('fs');
const logs = ['INFO', 'WARN', 'ERROR'];

const fd = fs.openSync('system.log', 'w');
logs.forEach((level) => {
    const entry = `${level}: System initialized\n`;
    // writeSync(fd, string, position, encoding)
    const bytesWritten = fs.writeSync(fd, entry);
    console.log(`Bytes written: ${bytesWritten}`);
});
fs.closeSync(fd);

const readFd = fs.openSync('system.log', 'r');
let totalContent = '';
let bytesRead = 0;
const buffer = Buffer.alloc(16);

do {
    bytesRead = fs.readSync(readFd, buffer, 0, buffer.length, null);
    totalContent += buffer.toString('utf8', 0, bytesRead);
} while (bytesRead > 0);

fs.closeSync(readFd);
console.log('File Content:\n' + totalContent);

Manual Asynchronous Buffer Operations

Asynchronous manual operations use callbacks to handle data processing without blocking the event loop.

const fs = require('fs');
const taskList = ['Task 1', 'Task 2', 'Task 3'];

function processWriteQueue(fd) {
    if (taskList.length > 0) {
        const currentTask = taskList.shift() + '\n';
        fs.write(fd, currentTask, (err, written) => {
            if (!err) {
                console.log(`Successfully wrote ${written} bytes.`);
                processWriteQueue(fd);
            }
        });
    } else {
        fs.close(fd, () => startAsyncRead());
    }
}

fs.open('tasks.txt', 'a', (err, fd) => {
    if (!err) processWriteQueue(fd);
});

function startAsyncRead() {
    fs.open('tasks.txt', 'r', (err, fd) => {
        const sharedBuffer = Buffer.alloc(10);
        function recursiveRead() {
            fs.read(fd, sharedBuffer, 0, sharedBuffer.length, null, (err, bytes) => {
                if (bytes > 0) {
                    process.stdout.write(sharedBuffer.toString('utf8', 0, bytes));
                    recursiveRead();
                } else {
                    fs.close(fd, () => {});
                }
            });
        }
        recursiveRead();
    });
}

Stream-Based File Access

Streams are the most efficient way to handle large files, as they process data in chunks rather than loading the entire file into memory.

const fs = require('fs');

const dataPoints = ['Point A', 'Point B', 'Point C'];
const writer = fs.createWriteStream('points.txt', { encoding: 'utf8' });

writer.on('finish', () => {
    console.log('Write stream completed.');
    initiateReadStream();
});

dataPoints.forEach(p => writer.write(`${p}\n`));
writer.end();

function initiateReadStream() {
    const reader = fs.createReadStream('points.txt', { encoding: 'utf8' });
    
    reader.on('data', (chunk) => {
        console.log('Received chunk:', chunk);
    });

    reader.on('end', () => {
        console.log('Read stream finished.');
    });

    reader.on('close', () => {
        console.log('Stream resources released.');
    });
}

Tags: Node.js File System javascript Backend Development fs-module

Posted on Tue, 20 Oct 2026 16:51:37 +0000 by jmurch