Reading Files
Blocking Read
Useful for configuration files during application startup when you need data before proceeding.
const fs = require('fs');
try {
const data = fs.readFileSync('config.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('Failed to read:', error.message);
}
Avoid in web servers or during request handling. Blocks the entire event loop until complete.
Asynchronous with Promises
The modern approach. Non-blocking with clean error handling using async/await syntax.
const fs = require('fs').promises;
async function readDocument(path) {
try {
const data = await fs.readFile(path, 'utf8');
return data;
} catch (error) {
if (error.code === 'ENOENT') {
console.error('File not found');
} else {
console.error('Read failed:', error);
}
throw error;
}
}
π― Pattern: Reading JSON Files
async function readJSON(filename) {
const data = await fs.readFile(filename, 'utf8');
return JSON.parse(data);
}
// Usage
const config = await readJSON('config.json');
Writing & Modifying
writeFile()
Creates new file or replaces existing content entirely.
await fs.writeFile('file.txt', 'content')
appendFile()
Adds content to end of file. Creates file if doesn't exist.
await fs.appendFile('log.txt', 'entry')
copyFile()
Copies file from source to destination.
await fs.copyFile('src.txt', 'dest.txt')
rename()
Moves or renames files and directories.
await fs.rename('old.txt', 'new.txt')
Practical Example: Logger
async function log(message) {
const timestamp = new Date().toISOString();
const entry = `[${timestamp}] ${message}\n`;
await fs.appendFile('app.log', entry, 'utf8');
}
// Usage
await log('Application started');
await log('User logged in');
Directory Operations
Creating Nested Structures
// Create single directory
await fs.mkdir('new-folder');
// Create nested directories (recursive)
await fs.mkdir('path/to/nested/folder', { recursive: true });
Reading Directory Contents
// Simple list
const files = await fs.readdir('./my-folder');
console.log(files); // ['file1.txt', 'file2.js']
// With file type information
const entries = await fs.readdir('./my-folder', { withFileTypes: true });
entries.forEach(entry => {
console.log(entry.name, entry.isDirectory() ? 'π' : 'π');
});
Recursive Listing Pattern
async function listFilesRecursively(dir, files = []) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await listFilesRecursively(fullPath, files);
} else {
files.push(fullPath);
}
}
return files;
}
Working with Streams
Readable Streams
const readStream = fs.createReadStream('large-file.txt');
readStream.on('data', chunk => {
console.log('Chunk:', chunk.length);
});
readStream.on('end', () => {
console.log('Complete');
});
Piping Streams
const read = fs.createReadStream('source.txt');
const write = fs.createWriteStream('dest.txt');
read.pipe(write);
write.on('finish', () => {
console.log('Copied!');
});
When to Use Streams
- β Files larger than 100MB
- β Processing data on the fly
- β Network responses
- β Real-time data processing
File Watching
// Watch a single file
fs.watch('config.txt', (eventType, filename) => {
console.log(`${filename} changed: ${eventType}`);
});
// Watch directory recursively
fs.watch('./src', { recursive: true }, (eventType, filename) => {
console.log(`${filename} was ${eventType}`);
});
// Detailed change info
fs.watchFile('important.txt', (current, previous) => {
console.log(`Modified: ${current.mtime}`);
});
Advanced Operations
File Descriptors
Low-level control for precise read/write operations.
const handle = await fs.open('data.txt', 'r');
try {
const buffer = Buffer.alloc(100);
const result = await handle.read(buffer, 0, 100, 0);
console.log(buffer.toString('utf8', 0, result.bytesRead));
} finally {
await handle.close(); // Always close!
}
File Permissions
// Make executable
await fs.chmod('script.sh', 0o755);
// Private file
await fs.chmod('secret.txt', 0o600);
Error Handling
Common Error Codes
ENOENT
File or directory does not exist
EACCES
Permission denied
EISDIR
Expected file, found directory
EEXIST
File already exists
Robust Error Handling Pattern
async function safeOperation(filepath) {
try {
return await fs.readFile(filepath, 'utf8');
} catch (error) {
switch (error.code) {
case 'ENOENT':
throw new Error(`File not found: ${filepath}`);
case 'EACCES':
throw new Error(`Permission denied: ${filepath}`);
case 'EISDIR':
throw new Error(`Expected file, got directory: ${filepath}`);
default:
throw new Error(`Unexpected error: ${error.message}`);
}
}
}
Best Practices
| Scenario | Approach | Reason |
|---|---|---|
| Application startup config | readFileSync | Need config before accepting requests |
| Web server requests | fs/promises | Don't block concurrent requests |
| Files > 100MB | createReadStream | Constant memory usage |
| One-time scripts | Sync methods | Simplicity over performance |
Always Close Handles
Use try/finally blocks to ensure file descriptors are closed, preventing memory leaks.
Validate Paths
Never use user input directly in file paths.
Use path.join() and
validation.