Node.js: File System Basics

Last updated: 2026-08-26

Charlie is a backend engineer responsible for maintaining the company’s log analysis platform. Every day at dawn, the system needs to process approximately 50,000 lines of server log files. Initially, he used fs.readFileSync to read the logs one by one, but the entire program froze completely during the reading process, causing all other requests to time out. After switching to asynchronous reading with fs.readFile, the program was able to respond to other requests while waiting for disk I/O, resulting in a 10-fold increase in overall throughput. This experience gave him a deep understanding of the difference between synchronous and asynchronous operations in the Node.js file system API.

1. What You'll Learn



2. Overview of the fs Module

The fs module is a built-in Node.js module for file system operations, providing capabilities such as file reading and writing, directory management, and permission checks. Each operation typically offers three styles: synchronous, asynchronous callback, and asynchronous Promise.

Feature Synchronous Method Asynchronous Callback Method fs.promises Method
Naming Feature xxxSync Suffix No suffix fs.promises.xxx
Return Value Returns the result directly undefined, obtained via a callback Returns Promise
Blocks the event loop Yes No No
Error Handling try/catch First parameter of the callback .catch() / try-catch
Recommended Use Cases Load Configuration on Startup Backward Compatibility Top Choice for New Projects
JAVASCRIPT
const fs = require('fs');

// Synchronize
const data = fs.readFileSync('config.json', 'utf8');

// Asynchronous Callbacks
fs.readFile('config.json', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

// Promise
const fsPromises = require('fs/promises');
fsPromises.readFile('config.json', 'utf8')
  .then(data => console.log(data))
  .catch(err => console.error(err));


3. Synchronous vs. Asynchronous Execution Sequences

Synchronous methods block the event loop and do not continue executing subsequent code until the file operation is complete. Asynchronous methods, on the other hand, return immediately and notify the result via a callback or a Promise once the file operation is complete.

100%
sequenceDiagram
    participant Main as Main Thread
    participant FS_Sync as Synchronous Read
    participant FS_Async as Asynchronous Reading
    participant Disk as Disk I/O

    Note over Main,Disk: Synchronous Execution Process
    Main->>FS_Sync: readFileSync('log.txt')
    FS_Sync->>Disk: Read a File(Blocking Wait)
    Disk-->>FS_Sync: Return Data
    FS_Sync-->>Main: Continue executing the following code
    Note right of Main: All other requests are on hold

    Note over Main,Disk: Asynchronous Execution Flow
    Main->>FS_Async: readFile('log.txt', callback)
    FS_Async->>Disk: Submit a read request
    FS_Async-->>Main: Return Now,Continue Execution
    Note right of Main: Can handle other requests
    Disk-->>FS_Async: I/O Done
    FS_Async-->>Main: Execute the callback function

▶ Example: Synchronous reading blocks the entire program

JAVASCRIPT
const fs = require('fs');

console.log('Start Reading...');
const data = fs.readFileSync('big-log.txt', 'utf8');
console.log('Reading complete,Number of lines:', data.split('\n').length);
console.log('This line must wait until the data has finished loading before it is executed.');
▶ Try it Yourself
TEXT 📖 Display only
Start Reading...
Reading complete,Number of lines:50000
This line must wait until the data has finished loading before it is executed.

▶ Example: Asynchronous Reading Without Blocking the Event Loop

JAVASCRIPT
const fs = require('fs');

console.log('Start Reading...');
fs.readFile('big-log.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log('Reading complete,Number of lines:', data.split('\n').length);
});
console.log('This line is executed immediately,No need to wait for the data to be read');
▶ Try it Yourself
TEXT 📖 Display only
Start Reading...
This line is executed immediately,No need to wait for the data to be read
Reading complete,Number of lines:50000


4. File Read and Write Operations

Method Parameters Purpose
fs.readFile(path, encoding, callback) Path, Encoding, Callback Asynchronous Reading of the Entire File
fs.readFileSync(path, encoding) Path, Encoding Read the entire file synchronously
fs.writeFile(path, data, encoding, callback) Path, Data, Encoding, Callback Asynchronous Write (Overwrite)
fs.writeFileSync(path, data, encoding) Path, Data, Encoding Write in sync (overwrite)
fs.appendFile(path, data, encoding, callback) Path, Data, Encoding, Callback Asynchronous Append
fs.appendFileSync(path, data, encoding) Path, Data, Encoding Synchronize Appended Content
fs.unlink(path, callback) Path, Callback Asynchronous File Deletion
fs.unlinkSync(path) Path Delete files simultaneously

▶ Example: Writing to and Appending to Files

JAVASCRIPT
const fs = require('fs');

fs.writeFile('output.txt', 'Content on the first line\n', 'utf8', (err) => {
  if (err) throw err;
  console.log('Write complete');

  fs.appendFile('output.txt', 'The second line added\n', 'utf8', (err) => {
    if (err) throw err;
    console.log('Addition Complete');

    fs.readFile('output.txt', 'utf8', (err, data) => {
      if (err) throw err;
      console.log('Document Content:\n', data);
    });
  });
});
▶ Try it Yourself
TEXT 📖 Display only
Write complete
Addition Complete
Document Content:
 Content on the first line
The second line added

▶ Example: Using fs.promises to Avoid Nested Callbacks

JAVASCRIPT
const fs = require('fs/promises');

async function writeAndRead() {
  try {
    await fs.writeFile('output.txt', 'Content on the first line\n', 'utf8');
    console.log('Write complete');
    await fs.appendFile('output.txt', 'The second line added\n', 'utf8');
    console.log('Addition Complete');
    const data = await fs.readFile('output.txt', 'utf8');
    console.log('Document Content:\n', data);
  } catch (err) {
    console.error('Operation Failed:', err.message);
  }
}

writeAndRead();
▶ Try it Yourself

▶ Example: Deleting a File

JAVASCRIPT
const fs = require('fs/promises');

async function deleteFile() {
  try {
    await fs.unlink('output.txt');
    console.log('The file has been deleted');
  } catch (err) {
    console.error('Deletion Failed:', err.message);
  }
}

deleteFile();
▶ Try it Yourself

5. Directory Operations

Method Parameters Purpose
fs.mkdir(path, options, callback) Path, {recursive}, Callback Create Directory
fs.readdir(path, options, callback) Path, {withFileTypes}, Callback List directory contents
fs.stat(path, callback) Path, Callback Get File/Directory Information
fs.existsSync(path) Path Check if the path exists

▶ Example: Creating a Directory and Listing Its Contents

JAVASCRIPT
const fs = require('fs/promises');

async function dirOperations() {
  try {
    await fs.mkdir('logs', { recursive: true });
    console.log('The directory was successfully created');

    await fs.writeFile('logs/app.log', '2025-01-01 Server started\n', 'utf8');
    await fs.writeFile('logs/error.log', '2025-01-01 Connection timeout\n', 'utf8');

    const files = await fs.readdir('logs');
    console.log('Table of Contents:', files);
  } catch (err) {
    console.error('Operation Failed:', err.message);
  }
}

dirOperations();
▶ Try it Yourself
TEXT 📖 Display only
The directory was successfully created
Table of Contents: [ 'app.log', 'error.log' ]

▶ Example: Determining whether a file or a directory

JAVASCRIPT
const fs = require('fs/promises');

async function checkType() {
  const stats = await fs.stat('logs');
  console.log('logs This is the table of contents:', stats.isDirectory());
  console.log('logs It is a file:', stats.isFile());

  const fileStats = await fs.stat('logs/app.log');
  console.log('app.log It is a file:', fileStats.isFile());
  console.log('File size:', fileStats.size, 'bytes');
}

checkType();
▶ Try it Yourself
TEXT 📖 Display only
logs This is the table of contents: true
logs It is a file: false
app.log It is a file: true
File size: 29 bytes


6. Comparison of Error-First Callbacks and Promises

The Node.js file system API follows the "error-first callback" convention: the first argument of the callback function is always an error object; if there is no error, it is null. fs.promises uses the standard Promise mechanism to handle errors.

Comparison Item Error-First Callback fs.promises
Function Signature (err, data) => {} Return Promise<data>
Error Judgment if (err) Check try/catch or .catch()
Nesting Issues Prone to Callback Hell async/await Flattening
Typical Scenarios Compatibility with Legacy Projects Recommendations for New Projects
Import Method require('fs') require('fs/promises')

▶ Example: A Comparison of Two Error-Handling Methods

JAVASCRIPT
const fsCallback = require('fs');
const fsPromise = require('fs/promises');

// Error-First Callback
fsCallback.readFile('not-exist.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('Callback Method - Error:', err.code);
    return;
  }
  console.log(data);
});

// Promise Method
async function readWithPromise() {
  try {
    const data = await fsPromise.readFile('not-exist.txt', 'utf8');
    console.log(data);
  } catch (err) {
    console.error('PromiseMethod - Error:', err.code);
  }
}

readWithPromise();
▶ Try it Yourself
TEXT 📖 Display only
Callback Method - Error: ENOENT
PromiseMethod - Error: ENOENT


7. File Encoding

Code Description Applicable Scenarios Example
'utf8' UTF-8 text encoding (default) Reading and writing text files Logs, configurations, JSON
'base64' Base64 encoding Image transfer, binary-to-text conversion Embedded images, email attachments
'binary' ('latin1') Raw Bytes Low-Level Binary File Operations Image and Archive Processing
null Return to Buffer Object Need to manipulate raw bytes File verification, stream processing

▶ Example: Reading the Same File with Different Encodings

JAVASCRIPT
const fs = require('fs/promises');

async function readEncodings() {
  await fs.writeFile('sample.txt', 'Hello The World', 'utf8');

  const utf8Data = await fs.readFile('sample.txt', 'utf8');
  console.log('UTF-8:', utf8Data);

  const base64Data = await fs.readFile('sample.txt', 'base64');
  console.log('Base64:', base64Data);

  const bufferData = await fs.readFile('sample.txt');
  console.log('Buffer:', bufferData);
  console.log('Buffer Hexadecimal:', bufferData.toString('hex'));
}

readEncodings();
▶ Try it Yourself
TEXT 📖 Display only
UTF-8: Hello The World
Base64: SGVsbG8g5LiW55WM
Buffer: <Buffer 48 65 6c 6c 6f 20 e4 b8 96 e7 95 8c>
Buffer Hexadecimal: 48656c6c6f20e4b896e7958c


8. Comprehensive Example: File Management Tool

Build a complete file management workflow: Create a directory → Write the configuration → Read and parse → Append to the log → List the contents.

JAVASCRIPT
const fs = require('fs/promises');
const path = require('path');

async function fileManager() {
  const dir = 'project-data';
  const configPath = path.join(dir, 'config.json');
  const logPath = path.join(dir, 'app.log');

  try {
    // Step 1: Create a Directory
    await fs.mkdir(dir, { recursive: true });
    console.log('✓ The table of contents has been created:', dir);

    // Step 2: Write to the configuration file
    const config = {
      appName: 'LogAnalyzer',
      version: '1.0.0',
      maxLines: 50000,
      encoding: 'utf8'
    };
    await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
    console.log('✓ The configuration file has been written.:', configPath);

    // Step 3: Read and Parse the Configuration
    const raw = await fs.readFile(configPath, 'utf8');
    const parsed = JSON.parse(raw);
    console.log('✓ Configuration loaded:', parsed.appName, 'v' + parsed.version);

    // Step 4: Add a log entry
    const timestamp = new Date().toISOString();
    await fs.appendFile(logPath, `[${timestamp}] Service started\n`, 'utf8');
    await fs.appendFile(logPath, `[${timestamp}] Config loaded: ${parsed.maxLines} lines\n`, 'utf8');
    console.log('✓ The log has been appended.:', logPath);

    // Step 5: List the contents of the table of contents
    const entries = await fs.readdir(dir, { withFileTypes: true });
    console.log('✓ Table of Contents:');
    for (const entry of entries) {
      const type = entry.isDirectory() ? '[DIR]' : '[FILE]';
      const stats = await fs.stat(path.join(dir, entry.name));
      console.log(`  ${type} ${entry.name} (${stats.size} bytes)`);
    }
  } catch (err) {
    console.error('✗ Operation Failed:', err.message);
  }
}

fileManager();
TEXT 📖 Display only
✓ The table of contents has been created: project-data
✓ The configuration file has been written.: project-data/config.json
✓ Configuration loaded: LogAnalyzer v1.0.0
✓ The log has been appended.: project-data/app.log
✓ Table of Contents:
  [FILE] app.log (106 bytes)
  [FILE] config.json (98 bytes)

❓ FAQ

Q When should synchronous methods be used?
A Use them only for one-time operations, such as loading configuration files, during application startup. Never use synchronous methods at runtime, as they will block the event loop.
Q Why is the first parameter of the callback err?
A This is the Node.js "error-first callback" convention, which requires developers to check for errors before processing data, thereby preventing exceptions from being overlooked.
Q What is the difference between fs.promises and fs?
A fs.promises (or require('fs/promises')) provides the same functionality but returns a Promise; async/await can be used as an alternative to nested callbacks; fs uses the callback style—both are functionally identical.
Q How do I determine whether a path is a file or a directory?
A Use fs.stat(path) to obtain the stats object, then call stats.isFile() to check if it is a file, and stats.isDirectory() to check if it is a directory.
Q Does readFile load the entire file into memory?
A Yes, readFile reads the entire file into memory. When working with large files, you should use fs.createReadStream to read them in streams to avoid a memory overflow.
Q What is the purpose of the recursive option in fs.mkdir?
A Setting { recursive: true } allows you to create multiple levels of nested directories at once, similar to mkdir -p, and it won’t report an error even if the directory already exists.

📖 Summary


📝 Exercises

  1. Complete all the code examples in this lesson and make sure each one runs correctly.
  2. Modify the comprehensive example and add your own extensions
  3. Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
  4. Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
  5. Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏