Node.js & Express
File System Module (fs)
Introduction to the File System Module
The fs (File System) module is one of Node.js's most important core modules. It provides an API for interacting with the file system, allowing you to read, write, delete, and manipulate files and directories.
Important: The fs module provides both synchronous and asynchronous methods. Use asynchronous methods in production to avoid blocking the event loop.
Importing the fs Module
// Callback-based API (traditional)
const fs = require('fs');
// Promise-based API (modern, recommended)
const fsPromises = require('fs').promises;
// Or using ES6 destructuring
const { readFile, writeFile } = require('fs').promises;
Reading Files
Node.js provides multiple ways to read files depending on your needs:
Asynchronous File Reading (Callback)
const fs = require('fs');
// Read file asynchronously with callback
fs.readFile('data.txt', 'utf8', (error, data) => {
if (error) {
console.error('Error reading file:', error);
return;
}
console.log('File contents:', data);
});
// Without encoding - returns Buffer
fs.readFile('image.png', (error, data) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('File size:', data.length, 'bytes');
console.log('Buffer:', data); // <Buffer 89 50 4e 47...>
});
Synchronous File Reading
const fs = require('fs');
try {
// Blocks execution until file is read
const data = fs.readFileSync('data.txt', 'utf8');
console.log('File contents:', data);
} catch (error) {
console.error('Error reading file:', error);
}
Warning: Avoid
readFileSync() in production servers as it blocks the event loop, preventing other operations from running.
Promise-based File Reading (Recommended)
const fs = require('fs').promises;
// Using promises with .then()
fs.readFile('data.txt', 'utf8')
.then(data => {
console.log('File contents:', data);
})
.catch(error => {
console.error('Error:', error);
});
// Using async/await (cleaner)
async function readData() {
try {
const data = await fs.readFile('data.txt', 'utf8');
console.log('File contents:', data);
return data;
} catch (error) {
console.error('Error reading file:', error);
throw error;
}
}
readData();
Writing Files
Writing to files is just as important as reading. Node.js provides methods to create or overwrite files:
Asynchronous File Writing
const fs = require('fs');
const content = 'Hello from Node.js!';
// Write file (creates if not exists, overwrites if exists)
fs.writeFile('output.txt', content, 'utf8', (error) => {
if (error) {
console.error('Error writing file:', error);
return;
}
console.log('File written successfully');
});
// Write JSON data
const jsonData = {
name: 'John Doe',
age: 30,
email: 'john@example.com'
};
fs.writeFile('user.json', JSON.stringify(jsonData, null, 2), 'utf8', (error) => {
if (error) {
console.error('Error:', error);
return;
}
console.log('JSON file saved');
});
Promise-based File Writing
const fs = require('fs').promises;
async function saveData() {
try {
const data = 'Important data to save';
await fs.writeFile('important.txt', data, 'utf8');
console.log('File saved successfully');
// Write JSON
const user = { name: 'Jane', age: 25 };
await fs.writeFile('user.json', JSON.stringify(user, null, 2));
console.log('JSON saved');
} catch (error) {
console.error('Error saving files:', error);
}
}
saveData();
Appending to Files
To add content to the end of an existing file without overwriting:
const fs = require('fs').promises;
async function appendLog() {
try {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] Application started\n`;
await fs.appendFile('app.log', logEntry, 'utf8');
console.log('Log entry added');
} catch (error) {
console.error('Error appending to file:', error);
}
}
appendLog();
// Multiple appends
async function createLog() {
try {
await fs.appendFile('events.log', 'Event 1\n');
await fs.appendFile('events.log', 'Event 2\n');
await fs.appendFile('events.log', 'Event 3\n');
console.log('All events logged');
} catch (error) {
console.error('Error:', error);
}
}
Checking File Existence
const fs = require('fs').promises;
async function checkFile(filename) {
try {
// fs.access throws error if file doesn't exist
await fs.access(filename);
console.log(`${filename} exists`);
return true;
} catch (error) {
console.log(`${filename} does not exist`);
return false;
}
}
checkFile('data.txt');
// Check if file is readable, writable
async function checkPermissions(filename) {
try {
// Check read permission
await fs.access(filename, fs.constants.R_OK);
console.log('File is readable');
// Check write permission
await fs.access(filename, fs.constants.W_OK);
console.log('File is writable');
} catch (error) {
console.error('Permission denied:', error.message);
}
}
Deleting Files
const fs = require('fs').promises;
async function deleteFile(filename) {
try {
await fs.unlink(filename);
console.log(`${filename} deleted successfully`);
} catch (error) {
if (error.code === 'ENOENT') {
console.error('File does not exist');
} else {
console.error('Error deleting file:', error);
}
}
}
deleteFile('old-file.txt');
// Delete multiple files
async function deleteMultiple(files) {
try {
const deletePromises = files.map(file => fs.unlink(file));
await Promise.all(deletePromises);
console.log('All files deleted');
} catch (error) {
console.error('Error deleting files:', error);
}
}
deleteMultiple(['temp1.txt', 'temp2.txt', 'temp3.txt']);
Renaming and Moving Files
const fs = require('fs').promises;
async function renameFile(oldPath, newPath) {
try {
await fs.rename(oldPath, newPath);
console.log(`Renamed ${oldPath} to ${newPath}`);
} catch (error) {
console.error('Error renaming file:', error);
}
}
// Rename file
renameFile('old-name.txt', 'new-name.txt');
// Move file to different directory
renameFile('data.txt', 'backup/data.txt');
// Move and rename simultaneously
async function moveAndRename() {
try {
await fs.rename('downloads/report.pdf', 'documents/monthly-report-2024.pdf');
console.log('File moved and renamed');
} catch (error) {
console.error('Error:', error);
}
}
Getting File Information
const fs = require('fs').promises;
async function getFileInfo(filename) {
try {
const stats = await fs.stat(filename);
console.log('File Stats:');
console.log(' Size:', stats.size, 'bytes');
console.log(' Created:', stats.birthtime);
console.log(' Modified:', stats.mtime);
console.log(' Is File:', stats.isFile());
console.log(' Is Directory:', stats.isDirectory());
// File size in human-readable format
const sizeInKB = (stats.size / 1024).toFixed(2);
const sizeInMB = (stats.size / (1024 * 1024)).toFixed(2);
console.log(` Size: ${sizeInKB} KB (${sizeInMB} MB)`);
} catch (error) {
console.error('Error getting file info:', error);
}
}
getFileInfo('large-file.zip');
Working with Directories
The fs module also provides methods for creating, reading, and removing directories:
Creating Directories
const fs = require('fs').promises;
// Create single directory
async function createDir() {
try {
await fs.mkdir('uploads');
console.log('Directory created');
} catch (error) {
if (error.code === 'EEXIST') {
console.log('Directory already exists');
} else {
console.error('Error:', error);
}
}
}
// Create nested directories (recursive)
async function createNestedDirs() {
try {
await fs.mkdir('data/users/profiles', { recursive: true });
console.log('Nested directories created');
} catch (error) {
console.error('Error:', error);
}
}
createNestedDirs();
Reading Directories
const fs = require('fs').promises;
const path = require('path');
// List all files in directory
async function listFiles(directory) {
try {
const files = await fs.readdir(directory);
console.log(`Files in ${directory}:`);
files.forEach(file => {
console.log(' -', file);
});
} catch (error) {
console.error('Error reading directory:', error);
}
}
listFiles('./');
// Get detailed information about each file
async function listFilesDetailed(directory) {
try {
const files = await fs.readdir(directory);
for (const file of files) {
const filePath = path.join(directory, file);
const stats = await fs.stat(filePath);
console.log(`${file}:`);
console.log(` Type: ${stats.isFile() ? 'File' : 'Directory'}`);
console.log(` Size: ${stats.size} bytes`);
console.log(` Modified: ${stats.mtime}`);
}
} catch (error) {
console.error('Error:', error);
}
}
Deleting Directories
const fs = require('fs').promises;
// Remove empty directory
async function removeDir(dirPath) {
try {
await fs.rmdir(dirPath);
console.log(`Directory ${dirPath} removed`);
} catch (error) {
if (error.code === 'ENOTEMPTY') {
console.error('Directory is not empty');
} else {
console.error('Error:', error);
}
}
}
// Remove directory and all contents (recursive)
async function removeDirRecursive(dirPath) {
try {
await fs.rm(dirPath, { recursive: true, force: true });
console.log(`Directory ${dirPath} and contents removed`);
} catch (error) {
console.error('Error:', error);
}
}
removeDirRecursive('temp-folder');
Watching Files and Directories
Monitor files and directories for changes in real-time:
const fs = require('fs');
// Watch a file for changes
const watcher = fs.watch('config.json', (eventType, filename) => {
console.log(`Event: ${eventType}`);
console.log(`File: ${filename}`);
if (eventType === 'change') {
console.log('File was modified');
} else if (eventType === 'rename') {
console.log('File was renamed or deleted');
}
});
// Stop watching after 60 seconds
setTimeout(() => {
watcher.close();
console.log('Stopped watching file');
}, 60000);
// Watch a directory
fs.watch('./data', { recursive: true }, (eventType, filename) => {
console.log(`Change detected in: ${filename}`);
});
Copying Files
const fs = require('fs').promises;
async function copyFile(source, destination) {
try {
await fs.copyFile(source, destination);
console.log(`Copied ${source} to ${destination}`);
} catch (error) {
console.error('Error copying file:', error);
}
}
copyFile('original.txt', 'backup/original-copy.txt');
// Copy with flags
async function copyWithOptions(source, destination) {
try {
// COPYFILE_EXCL: Fail if destination exists
await fs.copyFile(source, destination, fs.constants.COPYFILE_EXCL);
console.log('File copied (destination didn\'t exist)');
} catch (error) {
if (error.code === 'EEXIST') {
console.error('Destination file already exists');
} else {
console.error('Error:', error);
}
}
}
Practical Example: File Manager
const fs = require('fs').promises;
const path = require('path');
class FileManager {
async readJSON(filename) {
const data = await fs.readFile(filename, 'utf8');
return JSON.parse(data);
}
async writeJSON(filename, data) {
const json = JSON.stringify(data, null, 2);
await fs.writeFile(filename, json, 'utf8');
}
async copyDirectory(source, destination) {
await fs.mkdir(destination, { recursive: true });
const files = await fs.readdir(source);
for (const file of files) {
const sourcePath = path.join(source, file);
const destPath = path.join(destination, file);
const stats = await fs.stat(sourcePath);
if (stats.isDirectory()) {
await this.copyDirectory(sourcePath, destPath);
} else {
await fs.copyFile(sourcePath, destPath);
}
}
}
async getDirectorySize(dirPath) {
let totalSize = 0;
const files = await fs.readdir(dirPath);
for (const file of files) {
const filePath = path.join(dirPath, file);
const stats = await fs.stat(filePath);
if (stats.isFile()) {
totalSize += stats.size;
} else if (stats.isDirectory()) {
totalSize += await this.getDirectorySize(filePath);
}
}
return totalSize;
}
}
// Usage
const fm = new FileManager();
fm.getDirectorySize('./data').then(size => {
console.log(`Total size: ${(size / 1024 / 1024).toFixed(2)} MB`);
});
Practice Exercise
- Create a program that reads a text file and counts the number of words
- Write a function that creates a backup copy of a file with a timestamp
- Implement a log file manager that appends entries with timestamps
- Create a function that lists all files in a directory and its subdirectories
- Write a program that watches a configuration file and reloads settings when it changes
- Build a simple file organizer that moves files to folders based on their extensions
Summary
In this lesson, you learned:
- How to read files using callbacks, promises, and async/await
- Writing and appending to files
- Checking file existence and permissions
- Deleting, renaming, and copying files
- Getting file information with
fs.stat() - Creating, reading, and removing directories
- Watching files and directories for changes
- The difference between synchronous and asynchronous fs methods
- Best practices for file system operations in Node.js