1. What is the fs Module?

In Node.js, the File System (FS) is a core module for handling files and directories, typically accessed via the fs module. Think of it this way: if you write frontend code in a browser, you can only manipulate data in memory, but Node.js runs on the server and interacts directly with the computer’s hard drive. The fs module is Node.js’s “hard drive operation toolkit.”

For example, you can use it to read local JSON files, create folders, write logs, etc. The fs module’s APIs are divided into synchronous and asynchronous types. Synchronous methods block code execution until completion, while asynchronous methods do not, making them better for high-concurrency scenarios.

2. Synchronous vs. Asynchronous: Which to Choose?

Before writing code, clarify the differences:

  • Synchronous methods: Like queuing for milk tea—you must wait until you get it before proceeding. Simple and direct, but block subsequent code.
  • Asynchronous methods: Like ordering ahead—you can do other things while waiting for your drink. Non-blocking, more efficient, and ideal for Node.js’s single-threaded model.

Beginner advice: Prioritize asynchronous methods as they align with Node.js’s design philosophy. Use synchronous methods for simple logic (e.g., reading small files) for quick implementation.

3. Basic File Read/Write Operations

1. Import the fs Module

Start by importing the fs module:

const fs = require('fs'); // Import the core module

2. Asynchronous File Reading (readFile)

Use fs.readFile for asynchronous file reading:

fs.readFile('file-path', 'encoding', (err, data) => {
  // Callback executes after reading completes
  if (err) {
    console.error('Read failed:', err);
    return; // Terminate on error
  }
  console.log('File content:', data);
});

Parameter Explanation:
- file-path: Relative (e.g., './test.txt') or absolute path (e.g., '/Users/yourname/test.txt').
- encoding: E.g., 'utf8' (returns string); omit to get Buffer (binary data).
- callback: Takes err (error) and data (file content).

Example:
Create test.txt with content "Hello, Node.js!" and run:

const fs = require('fs');

// Asynchronously read test.txt (relative path)
fs.readFile('./test.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Read failed:', err);
    return;
  }
  console.log('File content:', data); // Output: File content: Hello, Node.js!
});

3. Synchronous File Reading (readFileSync)

For sequential execution, use fs.readFileSync with try/catch for errors:

try {
  const data = fs.readFileSync('./test.txt', 'utf8');
  console.log('Synchronous read content:', data);
} catch (err) {
  console.error('Synchronous read failed:', err);
}

Difference: Synchronous methods block code until completion. Use for simple logic (e.g., initializing config files).

4. Asynchronous File Writing (writeFile)

Use fs.writeFile to write to a file (overwrites existing content; creates if new):

fs.writeFile('output.txt', 'This is written content', (err) => {
  if (err) {
    console.error('Write failed:', err);
    return;
  }
  console.log('Write successful!');
});

Example: Generates output.txt with "This is written content".

Append Content: Use fs.appendFile instead:

fs.appendFile('output.txt', '\nThis is appended content', (err) => {
  if (err) {
    console.error('Append failed:', err);
    return;
  }
  console.log('Append successful!');
});

5. Path Handling: Avoid “File Not Found” Errors

Path handling is critical. Use Node.js’s path module for cross-platform safety:

const path = require('path');

Common Path Methods:
- path.join(__dirname, 'subdir', 'file.txt'): Safely joins paths (handles / or \ automatically).
- __dirname: Absolute path of the current module’s directory (dynamic, always the script’s folder).
- __filename: Absolute path of the current module (including filename).

Example:

const fs = require('fs');
const path = require('path');

// Construct path relative to the script's directory
const filePath = path.join(__dirname, 'data', 'test.txt');

fs.readFile(filePath, 'utf8', (err, data) => {
  if (err) {
    console.error('Path error:', err);
    return;
  }
  console.log('File read successfully:', data);
});

4. Directory Operations: Create, Read, Delete

1. Create Directory (mkdir)

Use fs.mkdir to create directories. For nested directories, enable recursive: true:

// Create single-level directory
fs.mkdir('./newDir', (err) => {
  if (err) {
    console.error('Directory creation failed:', err);
    return;
  }
  console.log('Directory created successfully!');
});

// Recursively create nested directories
fs.mkdir('./a/b/c', { recursive: true }, (err) => {
  if (err) {
    console.error('Nested directory creation failed:', err);
    return;
  }
  console.log('Nested directory created successfully!');
});

2. Read Directory Contents (readdir)

List files/subdirectories in a directory:

fs.readdir('./newDir', (err, files) => {
  if (err) {
    console.error('Directory read failed:', err);
    return;
  }
  console.log('Directory contents:', files); // E.g., ['file1.txt', 'subDir']
});

3. Delete Directory (rmdir)

fs.rmdir only deletes empty directories:

fs.rmdir('./emptyDir', (err) => {
  if (err) {
    console.error('Directory deletion failed:', err);
    return;
  }
  console.log('Directory deleted successfully!');
});

5. File Information and Permission Checks

1. Check File Existence (existsSync)

Quickly verify if a file/directory exists (synchronous):

const fs = require('fs');
const path = require('path');

const filePath = path.join(__dirname, 'test.txt');

if (fs.existsSync(filePath)) {
  console.log('File exists!');
  fs.readFile(filePath, 'utf8', (err, data) => { /* ... */ });
} else {
  console.log('File does not exist!');
}

2. Get File/Directory Info (stat)

Retrieve detailed file info (size, creation time, type):

fs.stat('./test.txt', (err, stats) => {
  if (err) {
    console.error('Failed to get info:', err);
    return;
  }
  console.log('Is file:', stats.isFile()); // true
  console.log('File size:', stats.size, 'bytes'); // E.g., 15 bytes
});

6. Common Issues and Solutions

1. File Path Errors

Issue: Relative paths are resolved from the directory where node is run, not the script’s location.
Fix: Use __dirname (script’s directory) or absolute paths with path.join:

// Incorrect (depends on execution location)
fs.readFile('test.txt', ...);

// Correct (uses script's directory)
fs.readFile(path.join(__dirname, 'test.txt'), ...);

2. Handling Large Files

Small files work with readFile/writeFile, but large files (videos, logs) consume memory. Use Streams for chunked processing:

const fs = require('fs');
const readStream = fs.createReadStream('largeFile.txt');
const writeStream = fs.createWriteStream('copiedFile.txt');

// Pipe data from readStream to writeStream
readStream.pipe(writeStream);

7. Summary

The fs module is fundamental for Node.js file system operations, enabling:
- Reading config/log files
- Creating/deleting directories/files
- Processing file content (read/write/append/copy)

Practice Recommendations:
1. Start with “Create → Write → Read → Delete” workflows
2. Use path module for cross-platform path handling
3. Compare synchronous/asynchronous methods to understand event loop and non-blocking I/O

Remember: Consistent practice, debugging, and experimentation are key to mastering file system operations!

Xiaoye