File System Module (fs)
The fs module is one of the most useful built-in modules in Node.js. It allows you to work with the file system on your computer: reading files, creating files, updating files, deleting files, and renaming files.
To use it, you must first require it:
const fs = require('fs');
Synchronous vs Asynchronous
Most methods in the fs module have both synchronous and asynchronous versions.
- Asynchronous methods take a callback function as the last argument. They are non-blocking.
- Synchronous methods block the execution until the operation completes. They usually end with
Sync.
Recommendation: Always use asynchronous methods in production to avoid blocking the event loop.
Reading Files
Asynchronous Read
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
Note: If you don’t specify the encoding (‘utf8’), you will get a Buffer object instead of a string.
Synchronous Read
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
Writing Files
Asynchronous Write
fs.writeFile() replaces the file and content if it exists. If the file doesn’t exist, a new file, containing the specified content, will be created.
const content = 'Hello, this is new content!';
fs.writeFile('output.txt', content, err => {
if (err) {
console.error(err);
return;
}
console.log('File written successfully');
});
Appending to Files
To add content to the end of a file without replacing it, use fs.appendFile().
fs.appendFile('output.txt', '\nThis is appended text.', err => {
if (err) throw err;
console.log('Content appended!');
});
Directories
Creating a Directory
if (!fs.existsSync('./new-folder')) {
fs.mkdir('./new-folder', err => {
if (err) throw err;
console.log('Directory created');
});
}
Reading a Directory
fs.readdir('./', (err, files) => {
if (err) console.log(err);
else console.log(files); // Returns an array of filenames
});
Removing Files and Directories
// Remove a file
fs.unlink('./output.txt', (err) => {
if (err) throw err;
console.log('File deleted');
});
// Remove an empty directory
fs.rmdir('./empty-folder', (err) => {
if (err) throw err;
console.log('Directory removed');
});
// Remove directory recursively (Node.js 14.14+)
fs.rm('./folder-with-files', { recursive: true, force: true }, (err) => {
if (err) throw err;
console.log('Directory removed recursively');
});
Promise-based API (fs/promises)
Modern Node.js provides a promise-based API that works beautifully with async/await.
const fs = require('fs/promises');
const path = require('path');
async function fileOperations() {
try {
// Read file
const content = await fs.readFile('input.txt', 'utf8');
console.log(content);
// Write file
await fs.writeFile('output.txt', 'New content');
// Append to file
await fs.appendFile('output.txt', '\nMore content');
// Get file stats
const stats = await fs.stat('output.txt');
console.log('File size:', stats.size);
console.log('Is file:', stats.isFile());
console.log('Is directory:', stats.isDirectory());
console.log('Created:', stats.birthtime);
console.log('Modified:', stats.mtime);
// Rename file
await fs.rename('output.txt', 'renamed.txt');
// Copy file
await fs.copyFile('renamed.txt', 'backup.txt');
// Create directory
await fs.mkdir('./new-folder', { recursive: true });
// Read directory
const files = await fs.readdir('./');
console.log('Files:', files);
} catch (error) {
console.error('Error:', error.message);
}
}
fileOperations();
Always use fs/promises with async/await for cleaner, more maintainable code in modern Node.js applications.
Watching Files for Changes
const fs = require('fs');
// Watch a file for changes
fs.watch('config.json', (eventType, filename) => {
console.log(`Event: ${eventType}, File: ${filename}`);
if (eventType === 'change') {
console.log('Config file was modified!');
// Reload configuration
}
});
// Watch a directory
fs.watch('./src', { recursive: true }, (eventType, filename) => {
console.log(`${eventType}: ${filename}`);
});
Working with JSON Files
const fs = require('fs/promises');
// Read and parse JSON
async function readJSON(filepath) {
const data = await fs.readFile(filepath, 'utf8');
return JSON.parse(data);
}
// Write JSON with formatting
async function writeJSON(filepath, data) {
const json = JSON.stringify(data, null, 2); // Pretty print with 2 spaces
await fs.writeFile(filepath, json);
}
// Usage
async function main() {
// Read existing data
const config = await readJSON('./config.json');
console.log('Current config:', config);
// Modify and save
config.updatedAt = new Date().toISOString();
await writeJSON('./config.json', config);
}
Practical Example: File-based Logger
const fs = require('fs/promises');
const path = require('path');
class FileLogger {
constructor(logDir = './logs') {
this.logDir = logDir;
this.init();
}
async init() {
// Ensure log directory exists
await fs.mkdir(this.logDir, { recursive: true });
}
getLogFileName() {
const date = new Date().toISOString().split('T')[0];
return path.join(this.logDir, `app-${date}.log`);
}
formatMessage(level, message) {
const timestamp = new Date().toISOString();
return `[${timestamp}] [${level.toUpperCase()}] ${message}\n`;
}
async log(level, message) {
const logFile = this.getLogFileName();
const formatted = this.formatMessage(level, message);
await fs.appendFile(logFile, formatted);
}
async info(message) {
await this.log('info', message);
}
async error(message) {
await this.log('error', message);
}
async warn(message) {
await this.log('warn', message);
}
// Get recent logs
async getRecentLogs(lines = 50) {
const logFile = this.getLogFileName();
try {
const content = await fs.readFile(logFile, 'utf8');
const logLines = content.trim().split('\n');
return logLines.slice(-lines);
} catch (err) {
return [];
}
}
// Clean old logs
async cleanOldLogs(daysToKeep = 30) {
const files = await fs.readdir(this.logDir);
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
for (const file of files) {
const filePath = path.join(this.logDir, file);
const stats = await fs.stat(filePath);
if (stats.mtime < cutoffDate) {
await fs.unlink(filePath);
console.log(`Deleted old log: ${file}`);
}
}
}
}
module.exports = FileLogger;
Summary
- Use asynchronous methods in production (avoid
Sync methods)
- Prefer
fs/promises with async/await for modern code
- Use
fs.watch() to monitor file/directory changes
fs.stat() provides file metadata (size, dates, type)
- Always handle errors when working with the file system
- Create reusable utilities for common patterns (logging, JSON handling)
Removing a Directory
if (fs.existsSync('./new-folder')) {
fs.rmdir('./new-folder', err => {
if (err) console.log(err);
else console.log('Directory removed');
});
}
Deleting Files
if (fs.existsSync('output.txt')) {
fs.unlink('output.txt', err => {
if (err) throw err;
console.log('File deleted');
});
}
Promise-based API
Node.js also provides a Promise-based API for fs, which is cleaner to use with async/await.
const fs = require('fs/promises');
async function readFile() {
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
readFile();
Streams
For large files, reading the whole file into memory (like readFile does) is inefficient. Streams allow you to process data piece by piece.
const fs = require('fs');
const readStream = fs.createReadStream('largefile.txt', { encoding: 'utf8' });
const writeStream = fs.createWriteStream('copy.txt');
readStream.on('data', (chunk) => {
console.log('--- NEW CHUNK ---');
console.log(chunk);
writeStream.write(chunk);
});
// Piping (easier way to copy)
// readStream.pipe(writeStream);
Summary
- Use
fs.readFile and fs.writeFile for simple file operations.
- Prefer asynchronous methods or the Promise API (
fs/promises) to avoid blocking.
- Use Streams for handling large datasets efficiently.
- Always handle errors (e.g., file not found, permission denied).