Complete Node.js File System Tutorial
Read, write, stream, and manage files in real-world Node.js applications
Hands-on
Production Ready
Best Practices
Table of Contents
1. Introduction & Setup
What is the FS Module?
The Node.js fs module lets you interact with files and directories: read, write, update, delete, inspect metadata, and manage folder structures.
Installation & Setup
// Built into Node.js - no install needed
const fsPromises = require('node:fs/promises'); // Promise-based (recommended)
const fs = require('node:fs'); // Callback/sync APIs
mkdir node-fs-tutorial
cd node-fs-tutorial
npm init -y
echo "Hello World" > test.txt
2. Reading Files
Basic Reading
const fs = require('node:fs/promises');
async function readFileExample() {
try {
const content = await fs.readFile('test.txt', 'utf8');
console.log(content);
const buffer = await fs.readFile('image.jpg');
console.log(buffer.length);
} catch (error) {
console.error(error.message);
}
}
Read JSON
const jsonContent = await fs.readFile('data.json', 'utf8');
const parsed = JSON.parse(jsonContent);
console.log(parsed.name);
Line-by-line Reading
const readline = require('node:readline');
const fsCb = require('node:fs');
const rl = readline.createInterface({ input: fsCb.createReadStream('large-file.txt'), crlfDelay: Infinity });
for await (const line of rl) { console.log(line); }
3. Writing Files
Basic Writing
await fs.writeFile('output.txt', 'Hello, Node.js!');
await fs.appendFile('output.txt', '\nAppended line');
await fs.writeFile('binary.bin', Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]));
Write JSON
const data = { users: [{ id: 1, name: 'Alice' }] };
await fs.writeFile('users.json', JSON.stringify(data, null, 2));
await fs.writeFile('users.min.json', JSON.stringify(data));
Conditional Writing
try { await fs.access('config.json'); }
catch { await fs.writeFile('config.json', JSON.stringify({ theme: 'dark' })); }
4. Working with Directories
await fs.mkdir('my-folder');
await fs.mkdir('parent/child/grandchild', { recursive: true });
const contents = await fs.readdir('parent');
const detailed = await fs.readdir('parent', { withFileTypes: true });
Recursive Traversal
async function listAllFiles(dir) {
const items = await fs.readdir(dir, { withFileTypes: true });
for (const item of items) {
const full = require('node:path').join(dir, item.name);
if (item.isDirectory()) await listAllFiles(full);
else console.log(full);
}
}
Copy Directory
async function copyDirectory(src, dst) {
await fs.mkdir(dst, { recursive: true });
const items = await fs.readdir(src, { withFileTypes: true });
for (const item of items) {
const path = require('node:path');
const s = path.join(src, item.name);
const d = path.join(dst, item.name);
if (item.isDirectory()) await copyDirectory(s, d);
else await fs.copyFile(s, d);
}
}
5. File Information & Permissions
const stats = await fs.stat('test.txt');
console.log(stats.size, stats.mtime, stats.isFile());
console.log(stats.mode.toString(8));
Permissions
await fs.writeFile('script.js', 'console.log("Hello");');
await fs.chmod('script.js', 0o755); // executable
await fs.chmod('script.js', 0o444); // read-only
await fs.chmod('script.js', 0o644); // restore
6. Working with File Paths
const path = require('node:path');
const filePath = '/users/john/projects/app/config.json';
console.log(path.dirname(filePath));
console.log(path.basename(filePath));
console.log(path.extname(filePath));
console.log(path.join(__dirname, 'config', 'app.json'));
console.log(path.resolve('relative/path/file.txt'));
Cross-platform Tip
// Avoid manual separators
const good = path.join(__dirname, 'data', 'file.txt');
7. Streaming Large Files
const fsCb = require('node:fs');
const readStream = fsCb.createReadStream('large-file.log', { highWaterMark: 64 * 1024 });
const writeStream = fsCb.createWriteStream('output.log');
readStream.pipe(writeStream);
Compress While Streaming
const zlib = require('node:zlib');
fsCb.createReadStream('large-file.log')
.pipe(zlib.createGzip())
.pipe(fsCb.createWriteStream('large-file.log.gz'));
8. Real-World Examples
File Backup System
class BackupSystem {
async backup() { // create timestamped backup + manifest }
async copyDirectory(src, dest) { // recursive copy }
}
Config Manager
class ConfigManager {
async load() { // load or create default config }
async save() { // write + backup }
get(key) { // dot path access }
}
Log Rotator
class LogRotator {
async rotate(logFile) { // rotate by size threshold }
async rotateFiles(baseName) { // keep max backup files }
}
9. Error Handling Best Practices
class FileSystemError extends Error {
constructor(message, code, operation, path) {
super(message);
this.code = code; this.operation = operation; this.path = path;
}
}
Retry with Backoff
async function withRetry(operation, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try { return await operation(); }
catch (e) { if (i === retries - 1) throw e; await new Promise(r => setTimeout(r, delay)); delay *= 2; }
}
}
10. Performance Tips
Batch Operations
// Parallel reads
const results = await Promise.all(files.map(f => fs.readFile(f, 'utf8')));
Simple File Cache
class FileCache {
constructor(maxSize = 100) { this.cache = new Map(); this.maxSize = maxSize; }
async readFile(filePath) { // cache hit/miss logic }
}
Memory-efficient line processing
// process stream chunks without loading full file
await processLargeFileEfficiently('huge-file.log', line => {
if (line.includes('ERROR')) console.log(line);
});
Quick Reference Cheatsheet
await fs.readFile('file.txt', 'utf8');
await fs.writeFile('file.txt', 'content');
await fs.appendFile('file.txt', 'more\n');
await fs.unlink('file.txt');
await fs.mkdir('folder', { recursive: true });
await fs.readdir('folder');
await fs.copyFile('a.txt', 'b.txt');
await fs.rename('old.txt', 'new.txt');
10 Interview Questions + 10 MCQs
Interview Pattern 10 Q&A1Why prefer fs/promises API?easy
Answer: Cleaner async/await code, better readability, and easier error handling.
2When should you use streams instead of readFile?easy
Answer: For large files to reduce memory usage and improve throughput.
3Difference between writeFile and appendFile?easy
Answer: writeFile overwrites file; appendFile adds content at end.
4Why use path.join over manual string concatenation?medium
Answer: path.join is cross-platform safe and avoids separator issues.
5How to check if file exists without race conditions?medium
Answer: Prefer trying the operation and handling ENOENT, or use access carefully.
6What does recursive option in mkdir do?easy
Answer: Creates nested directories if missing.
7How do permissions like 755/644 differ?medium
Answer: 755 allows execute for owner/group/others; 644 is read/write owner and read-only others.
8Why implement retry logic in FS operations?medium
Answer: To handle transient errors (locks, temporary IO issues) with resilience.
9How to process huge logs efficiently?hard
Answer: Use streaming/chunk processing and line-wise handling instead of loading full files.
10What is a safe backup strategy before overwrite?hard
Answer: Copy existing file/folder to timestamped backup and verify write success.
10 Node.js File System MCQs
1
Which import is promise-based fs API?
Explanation: Promise API is available in
node:fs/promises.2
Which method appends to file?
Explanation:
appendFile adds content at end.3
Best for huge file processing?
Explanation: Streams are memory efficient.
4
Option to create nested directories?
Explanation: Use
{ recursive: true } in mkdir.5
Which module handles cross-platform paths?
Explanation: Use Node
path module.6
Which call gets file metadata?
Explanation:
fs.stat returns file stats object.7
Permission mode 0o444 means:
Explanation: 444 is read-only for owner/group/others.
8
Which method removes a file?
Explanation: Use
unlink for files.9
Best strategy for transient IO failures?
Explanation: Backoff retries improve resilience.
10
Which is best for checking detailed directory entries?
Explanation: withFileTypes returns Dirent objects for files/directories.