Node.js & Express

File System Module (fs)

18 min Lesson 5 of 40

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

  1. Create a program that reads a text file and counts the number of words
  2. Write a function that creates a backup copy of a file with a timestamp
  3. Implement a log file manager that appends entries with timestamps
  4. Create a function that lists all files in a directory and its subdirectories
  5. Write a program that watches a configuration file and reloads settings when it changes
  6. 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