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
- Use
fs.readFile/fs.writeFile/fs.appendFile/fs.unlinkto perform file operations - Use
fs.mkdir/fs.readdir/fs.stat/fs.existsSyncto perform directory operations - Distinguish between the differences in execution between synchronous and asynchronous methods
- Understanding the "Error-First Callback"
(err, data)Model - Use the
fs.promisesAPI to perform file operations using Promises - Select the appropriate file encoding (utf8 / base64 / binary)
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 |
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.
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
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.');
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
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');
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
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);
});
});
});
Write complete
Addition Complete
Document Content:
Content on the first line
The second line added
▶ Example: Using fs.promises to Avoid Nested Callbacks
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();
▶ Example: Deleting a File
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();
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
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();
The directory was successfully created
Table of Contents: [ 'app.log', 'error.log' ]
▶ Example: Determining whether a file or a directory
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();
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
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();
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
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();
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.
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();
✓ 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
err?fs.promises and fs?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.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.readFile load the entire file into memory?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.recursive option in fs.mkdir?{ 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
- Key Concepts and How to Apply Them
- Key Concepts and Usage of the fs Module Overview
- Core Concepts and Usage of Synchronous vs. Asynchronous Execution Sequences
- Core Concepts and Usage of File Read and Write Operations
- Core Concepts and Usage of Directory Operations
- Key Concepts and Usage of Error-First Callbacks vs. Promises
- Core Concepts and Usage of File Encoding
- Comprehensive Example: Core Concepts and Usage of File Management Tools
📝 Exercises
- Complete all the code examples in this lesson and make sure each one runs correctly.
- Modify the comprehensive example and add your own extensions
- Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
- Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
- Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.