Chapter 24: Node.js File System Module
1. What is the File System module?
node:fs is the built-in module that lets your Node.js program read files, write files, create/delete directories, check file existence, get file stats, watch for changes, and more.
It is one of the most frequently used core modules in backend development.
Important facts (2025–2026):
-
There are three main APIs:
Style Syntax example When to use it today Recommendation Callback fs.readFile(…, callback) Very old code only Avoid Synchronous fs.readFileSync(…) CLI tools, scripts, startup config Use carefully Promise-based import fs from ‘node:fs/promises’ Almost all modern code Best choice -
Always use the node: prefix in imports → import fs from ‘node:fs/promises’ (modern & safe style)
-
Never block the event loop with heavy synchronous file operations in a server
2. Modern recommended way – Promise-based API
This is how most professional code is written in 2025–2026.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// file-system-modern.js import fs from 'node:fs/promises'; import path from 'node:path'; async function example() { try { // ─── Reading files ─────────────────────────────────────── const content = await fs.readFile('notes.txt', 'utf-8'); console.log('File content:', content); // ─── Writing files ─────────────────────────────────────── await fs.writeFile('output.txt', 'Hello from async fs!', 'utf-8'); console.log('File written successfully'); // ─── Appending ─────────────────────────────────────────── await fs.appendFile('log.txt', `${new Date().toISOString()} - User logged in\n`); // ─── Creating directory (recursive) ────────────────────── await fs.mkdir('logs/2026/02', { recursive: true }); console.log('Directory created'); // ─── Reading directory ─────────────────────────────────── const files = await fs.readdir('src', { withFileTypes: true }); for (const entry of files) { const type = entry.isDirectory() ? 'DIR' : entry.isFile() ? 'FILE' : 'OTHER'; console.log(`${type.padEnd(6)} → ${entry.name}`); } // ─── Check if file exists ──────────────────────────────── try { await fs.access('config.json'); console.log('config.json exists'); } catch { console.log('config.json does NOT exist'); } // ─── Get file stats ────────────────────────────────────── const stats = await fs.stat('notes.txt'); console.log('Size:', stats.size, 'bytes'); console.log('Created:', stats.birthtime); console.log('Modified:', stats.mtime); console.log('Is file?', stats.isFile()); console.log('Is directory?', stats.isDirectory()); } catch (err) { console.error('File system error:', err.message); } } example(); |
Run:
|
0 1 2 3 4 5 6 |
node file-system-modern.js |
3. Very common real-world patterns
Pattern 1 – Safe JSON config loading (very frequent)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async function loadConfig() { try { const raw = await fs.readFile('config.json', 'utf-8'); return JSON.parse(raw); } catch (err) { if (err.code === 'ENOENT') { console.warn('config.json not found → using defaults'); return { port: 3000, debug: false }; } throw err; // other errors are real problems } } |
Pattern 2 – Writing structured logs
|
0 1 2 3 4 5 6 7 8 9 |
async function logEvent(event) { const line = `${new Date().toISOString()} | ${event.level} | ${event.message}\n`; await fs.appendFile('app.log', line); } |
Pattern 3 – Copy file with progress feedback
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
async function copyFileWithFeedback(src, dest) { try { const data = await fs.readFile(src); await fs.writeFile(dest, data); console.log(`Successfully copied ${src} → ${dest}`); } catch (err) { console.error(`Copy failed: ${err.message}`); } } |
Pattern 4 – Read all JSON files in a folder
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
async function loadAllConfigs() { const files = await fs.readdir('configs', { withFileTypes: true }); const configs = []; for (const file of files) { if (file.isFile() && file.name.endsWith('.json')) { const content = await fs.readFile(path.join('configs', file.name), 'utf-8'); configs.push(JSON.parse(content)); } } return configs; } |
4. Synchronous API – when & how to use it safely
Only use synchronous methods in:
- CLI tools
- Build scripts
- Startup / configuration phase (before server starts)
- Tests
Never use readFileSync / writeFileSync inside request handlers!
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// OK in CLI or startup script import fs from 'node:fs'; const config = JSON.parse(fs.readFileSync('config.json', 'utf-8')); console.log('Config loaded synchronously:', config); // BAD in a server! app.get('/profile', (req, res) => { const data = fs.readFileSync('users.json'); // ← blocks entire server! // ... }); |
5. Very important error codes you should know
| Error code | Meaning | Common action |
|---|---|---|
| ENOENT | No such file or directory | File not found → use defaults / 404 |
| EEXIST | File / directory already exists | Skip creation or overwrite if safe |
| EACCES | Permission denied | Log error, tell user to check permissions |
| EISDIR | Is a directory | You tried to read a folder as a file |
| ENOTDIR | Not a directory | You tried to treat a file as a folder |
Best practice – handle common cases
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
async function safeRead(filePath) { try { return await fs.readFile(filePath, 'utf-8'); } catch (err) { if (err.code === 'ENOENT') { return null; // or default content } throw err; } } |
6. Watching for file changes (very useful for dev tools)
|
0 1 2 3 4 5 6 7 8 9 10 11 |
import fs from 'node:fs'; fs.watch('config.json', (eventType, filename) => { console.log(`Event: ${eventType}, File: ${filename}`); // → could reload config, restart something, etc. }); |
More powerful watcher (2026 style)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
import { watch } from 'node:fs'; const watcher = watch('src', { recursive: true }); watcher.on('change', (eventType, filename) => { console.log(`File changed: ${filename} (${eventType})`); }); |
Summary – Decision guide 2025–2026
| Situation | Recommended API | Why? |
|---|---|---|
| Modern server / API code | node:fs/promises + async/await | Clean, non-blocking, error handling easy |
| CLI tools / scripts / build steps | fs.readFileSync / fs.writeFileSync | Simpler, no async boilerplate |
| Startup config loading | Either (sync is acceptable here) | Runs once before server starts |
| Watching files (hot reload, config) | fs.watch | Built-in, lightweight |
| Very large files (> few hundred MB) | Streams (createReadStream) | Memory efficient |
Which part of the File System module would you like to explore much deeper next?
- Streams – reading/writing large files efficiently
- File watching in real applications (config reload, file upload watchers)
- Atomic file operations (safe write + rename pattern)
- Temporary files & cleanup
- Complete example – config loader + logger + migration runner
Just tell me which direction feels most useful — I’ll continue with detailed, production-ready examples. 😊
