Skip to content

Node.js File System

Node.js provides a built-in module called fs (File System) for interacting with the file system on the operating system. It allows you to read, write, update, delete, and manage files and directories.

The fs module can be imported using require:

const fs = require('fs');

Alternatively, for promise-based operations (recommended for modern async code):

const fsPromises = require('fs').promises;
// or using destructuring:
const { readFile, writeFile } = require('fs').promises;

Most methods in the fs module have both synchronous and asynchronous forms.

  • Asynchronous methods (e.g., fs.readFile(), fs.writeFile()): These methods perform the operation in the background without blocking the Node.js event loop. They typically accept a callback function as the last argument, which is executed upon completion (or error). The first argument to the callback is usually an error object (null if successful). This is the preferred approach for I/O operations in Node.js servers to maintain concurrency and responsiveness.
  • Synchronous methods (e.g., fs.readFileSync(), fs.writeFileSync()): These methods block the entire Node.js process until the file operation completes. While simpler for scripting or command-line tools, avoid using synchronous methods in web servers or applications that handle concurrent requests, as they will halt execution and severely impact performance.
  • Promise-based methods (fs.promises): These provide an alternative asynchronous interface using JavaScript Promises, often used with async/await syntax for cleaner asynchronous code compared to nested callbacks. Example: await fsPromises.readFile(...).

First, create a text file named input.txt with the following content:

Node.js Fundamentals
Learning about the file system module.

Now, create a JavaScript file named main.js:

const fs = require('fs');
// Asynchronous read using callback
console.log('Starting asynchronous read...');
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file asynchronously:', err);
return;
}
console.log('Asynchronous Read Content:\n' + data);
});
console.log('... Asynchronous read initiated, continuing execution ...');
// Synchronous read
console.log('\nStarting synchronous read...');
try {
const dataSync = fs.readFileSync('input.txt', 'utf8');
console.log('Synchronous Read Content:\n' + dataSync);
} catch (err) {
console.error('Error reading file synchronously:', err);
}
console.log('... Synchronous read finished ...');
console.log('\nProgram Ended');

Run the script:

$ node main.js

Expected Output (order might vary slightly for async part):

Starting asynchronous read...
... Asynchronous read initiated, continuing execution ...
Starting synchronous read...
Synchronous Read Content:
Node.js Fundamentals
Learning about the file system module.
... Synchronous read finished ...
Program Ended
Asynchronous Read Content:
Node.js Fundamentals
Learning about the file system module.

Notice how ‘continuing execution’ and the synchronous read logs appear before the asynchronous read content. This demonstrates the non-blocking nature of asynchronous operations.

Let’s explore some key fs methods using asynchronous examples with callbacks. Remember that promise-based versions (fs.promises) are often preferred in modern code.

Syntax: fs.open(path, flags[, mode], callback)

  • path: Path to the file.
  • flags: Specifies how the file should be opened (e.g., ‘r’ for read, ‘w’ for write, ‘a’ for append). See table below.
  • mode (optional): Sets file permissions if the file is created (default: 0o666).
  • callback: Function called with (err, fd), where fd is the file descriptor (a number representing the opened file).
FlagDescription
'r'Open file for reading. An error occurs if the file does not exist.
'r+'Open file for reading and writing. An error occurs if the file does not exist.
'w'Open file for writing. The file is created (if it does not exist) or truncated (if it exists).
'w+'Open file for reading and writing. Created or truncated.
'a'Open file for appending. The file is created if it does not exist.
'a+'Open file for reading and appending. Created if non-existent.

Example:

const fs = require('fs');
console.log('Going to open input.txt for reading...');
fs.open('input.txt', 'r', (err, fd) => {
if (err) {
return console.error('Error opening file:', err);
}
console.log('File opened successfully! File descriptor:', fd);
// Remember to close the file descriptor when done
fs.close(fd, (closeErr) => {
if (closeErr) {
console.error('Error closing file:', closeErr);
}
console.log('File closed successfully.');
});
});

Note: Using higher-level functions like fs.readFile or fs.writeFile is often simpler as they handle opening and closing the file automatically.

Syntax: fs.stat(path, callback)

  • path: Path to the file or directory.
  • callback: Function called with (err, stats), where stats is an fs.Stats object containing file details.

The stats object has useful properties (like size, mtime) and methods:

MethodDescription
stats.isFile()Returns true if it’s a regular file.
stats.isDirectory()Returns true if it’s a directory.
stats.isSymbolicLink()Returns true if it’s a symbolic link.

Example:

const fs = require('fs');
console.log('Getting information for input.txt...');
fs.stat('input.txt', (err, stats) => {
if (err) {
return console.error('Error getting file stats:', err);
}
console.log('File Stats:', stats);
console.log('\nIs File?', stats.isFile());
console.log('Is Directory?', stats.isDirectory());
console.log('Size:', stats.size, 'bytes');
console.log('Last Modified:', stats.mtime);
});

Syntax: fs.writeFile(file, data[, options], callback)

  • file: Path to the file.
  • data: The data to write (String, Buffer, TypedArray, DataView).
  • options (optional): Can be an encoding string (default ‘utf8’) or an object { encoding, mode, flag } (default flag ‘w’).
  • callback: Function called with (err) upon completion.

This method overwrites the file if it exists. Use flag 'a' (or fs.appendFile) to append.

Example:

const fs = require('fs');
const content = 'Writing some data using fs.writeFile!';
console.log('Going to write to output.txt');
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
return console.error('Error writing file:', err);
}
console.log('Data written successfully!');
// Verify by reading back
fs.readFile('output.txt', 'utf8', (readErr, data) => {
if (readErr) {
return console.error('Error reading back file:', readErr);
}
console.log('Read back content: ' + data);
});
});

Syntax: fs.readFile(path[, options], callback)

  • path: Path to the file.
  • options (optional): Can be an encoding string (e.g., ‘utf8’) or an object { encoding, flag }. If encoding is provided, data in the callback will be a string; otherwise, it’s a Buffer.
  • callback: Function called with (err, data).

Example (revisiting the earlier async example):

const fs = require('fs');
console.log('Going to read input.txt');
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
return console.error('Error reading file:', err);
}
console.log('File content:\n' + data);
});

If you omit the encoding, readFile returns a Buffer. Buffers are Node.js’s way of handling binary data.

const fs = require('fs');
fs.readFile('input.txt', (err, dataBuffer) => { // No encoding specified
if (err) {
return console.error(err);
}
console.log('Data as Buffer:', dataBuffer);
console.log('Data as String:', dataBuffer.toString('utf8'));
// Create Buffers directly (modern way)
const bufFromArray = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]); // from byte array
console.log(bufFromArray.toString()); // Output: hello
const bufFromString = Buffer.from('world', 'utf8');
console.log(bufFromString);
});

Note: The old new Buffer() constructor is deprecated due to security concerns. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.

Syntax: fs.unlink(path, callback)

  • path: Path to the file to delete.
  • callback: Function called with (err).

Example (use with caution!):

const fs = require('fs');
// First, create a file to delete
fs.writeFile('temp_to_delete.txt', 'Delete me', (err) => {
if (err) return console.error('Error creating temp file:', err);
console.log('Temporary file created.');
// Now delete it
console.log('Going to delete temp_to_delete.txt');
fs.unlink('temp_to_delete.txt', (unlinkErr) => {
if (unlinkErr) {
return console.error('Error deleting file:', unlinkErr);
}
console.log('File deleted successfully!');
});
});

Syntax: fs.mkdir(path[, options], callback)

  • path: Path of the directory to create.
  • options (optional): Can be mode (permissions, default 0o777) or an object { recursive, mode }. recursive: true allows creating parent directories if needed (like mkdir -p).
  • callback: Function called with (err).

Example:

const fs = require('fs');
const path = require('path'); // Use path module for cross-platform compatibility
const dirPath = path.join('.', 'my_new_directory', 'nested'); // Creates './my_new_directory/nested'
console.log(`Going to create directory: ${dirPath}`);
// Use { recursive: true } to create parent directories as needed
fs.mkdir(dirPath, { recursive: true }, (err) => {
if (err) {
return console.error('Error creating directory:', err);
}
console.log('Directory created successfully!');
});

Syntax: fs.readdir(path[, options], callback)

  • path: Path of the directory to read.
  • options (optional): Encoding (default ‘utf8’) or object { encoding, withFileTypes }. withFileTypes: true returns fs.Dirent objects instead of just filenames.
  • callback: Function called with (err, files), where files is an array of filenames (strings) or fs.Dirent objects.

Example:

const fs = require('fs');
console.log('Going to read contents of current directory (.)');
fs.readdir('.', { withFileTypes: true }, (err, files) => {
if (err) {
return console.error('Error reading directory:', err);
}
console.log('\nDirectory Contents:');
files.forEach(dirent => {
const type = dirent.isDirectory() ? 'Dir' : 'File';
console.log(` - ${dirent.name} (${type})`);
});
});

Syntax (fs.rmdir - older, for empty dirs): fs.rmdir(path, callback)

Syntax (fs.rm - newer, recommended): fs.rm(path[, options], callback)

  • path: Path of the directory to remove.
  • options (for fs.rm): Object like { recursive, force }. recursive: true allows removing non-empty directories.
  • callback: Function called with (err).

Example (using fs.rm):

const fs = require('fs');
const path = require('path');
const dirToRemove = path.join('.', 'my_new_directory');
console.log(`Going to remove directory: ${dirToRemove}`);
// Use { recursive: true, force: true } to remove non-empty directory
// Use force with caution, it suppresses some errors.
fs.rm(dirToRemove, { recursive: true }, (err) => {
if (err) {
// Check if error is because the directory doesn't exist (might be ok)
if (err.code === 'ENOENT') {
console.log('Directory does not exist, nothing to remove.');
} else {
return console.error('Error removing directory:', err);
}
} else {
console.log('Directory removed successfully!');
}
});

The fs module contains many more functions for file system interaction (renaming, changing permissions, watching for changes, etc.). Refer to the official Node.js documentation for a complete list and detailed descriptions: