|$ curl https://forge-ai.dev/api/markdown?path=docs/nodejs/filesystem
$cat docs/file-system.md
updated Recently·24 min read·published

File System

Node.jsFile SystemIntermediate🎯Free Tools
Introduction

The file system module is one of the most frequently used parts of Node.js. Modern applications should prefer the promise-based fs/promises API over callback-based fs or synchronous fs.*Sync methods. Promises compose better with async/await and do not block the event loop.

File system operations in Node.js run on the libuv thread pool. While this prevents direct event loop blocking, excessive file I/O can still saturate the thread pool and delay other operations like DNS lookups and cryptographic hashing.

This guide covers reading, writing, streaming, watching, and managing files and directories safely in production.

Reading and Writing Files

Use readFile for small files that fit comfortably in memory. For large files, use streams to keep memory usage constant. Always handle errors and validate paths.

read-write.js
JavaScript
1const fs = require('fs/promises');
2
3async function readConfig(path) {
4 try {
5 const data = await fs.readFile(path, 'utf8');
6 return JSON.parse(data);
7 } catch (err) {
8 if (err.code === 'ENOENT') {
9 console.error('Config file not found:', path);
10 return null;
11 }
12 throw err;
13 }
14}
15
16async function writeConfig(path, config) {
17 await fs.writeFile(path, JSON.stringify(config, null, 2), {
18 encoding: 'utf8',
19 mode: 0o600 // readable/writable by owner only
20 });
21}

warning

Never use readFile on files uploaded by users or files of unknown size. A single large file can exhaust process memory.
Streaming File Operations

Streams are the right tool for large files. The stream/promises pipeline helper handles errors, backpressure, and resource cleanup automatically.

file-streams.js
JavaScript
1const fs = require('fs');
2const { pipeline } = require('stream/promises');
3const zlib = require('zlib');
4
5async function compressLog(inputPath, outputPath) {
6 await pipeline(
7 fs.createReadStream(inputPath),
8 zlib.createGzip(),
9 fs.createWriteStream(outputPath)
10 );
11}
12
13compressLog('./app.log', './app.log.gz')
14 .then(() => console.log('Compressed'))
15 .catch((err) => {
16 console.error('Compression failed:', err);
17 process.exit(1);
18 });

best practice

Use pipeline instead of .pipe() for file streams. Pipeline forwards errors and destroys streams on failure, preventing file descriptor leaks.
Working with Directories

Node.js 20+ provides recursive directory creation and removal natively. The fs.opendir API lets you iterate over directory entries without loading the entire listing into memory.

directories.js
JavaScript
1const fs = require('fs/promises');
2const path = require('path');
3
4async function ensureDir(dirPath) {
5 await fs.mkdir(dirPath, { recursive: true });
6}
7
8async function* walk(dir) {
9 const entries = await fs.opendir(dir);
10 for await (const entry of entries) {
11 const fullPath = path.join(dir, entry.name);
12 if (entry.isDirectory()) {
13 yield* walk(fullPath);
14 } else {
15 yield fullPath;
16 }
17 }
18}
19
20async function countFiles(dir) {
21 let count = 0;
22 for await (const file of walk(dir)) {
23 console.log(file);
24 count++;
25 }
26 return count;
27}

info

Use opendir or readdir with streaming for large directories. Loading millions of filenames into an array consumes excessive memory.
Watching Files and Directories

fs.watch uses platform-specific notification mechanisms. It can emit multiple events for a single change and may not work reliably across network filesystems. For robust watching, consider fs.watchFile polling or dedicated libraries like Chokidar.

watch.js
JavaScript
1const fs = require('fs/promises');
2
3async function watchFile(filePath) {
4 const watcher = fs.watch(filePath);
5
6 try {
7 for await (const event of watcher) {
8 console.log('Event:', event.eventType, 'File:', event.filename);
9 }
10 } catch (err) {
11 console.error('Watch error:', err);
12 } finally {
13 watcher.close();
14 }
15}
📝

note

Debounce watch events before reacting. A single save can trigger rename and change events in rapid succession.
Permissions and Security

File system security involves more than reading and writing. Create files with restrictive permissions, validate paths to prevent directory traversal, and avoid executing files from untrusted sources.

path-safety.js
JavaScript
1const path = require('path');
2
3function safeJoin(base, target) {
4 const resolved = path.resolve(base, target);
5 if (!resolved.startsWith(path.resolve(base))) {
6 throw new Error('Directory traversal detected');
7 }
8 return resolved;
9}
10
11// safeJoin('/data', '../etc/passwd') throws
12// safeJoin('/data', 'user/profile.json') returns /data/user/profile.json

warning

Never construct file paths directly from user input. Always resolve and validate that the final path stays within the intended base directory.
Common Error Codes
CodeMeaningTypical Cause
ENOENTNo such file or directoryMissing file, typo in path
EACCESPermission deniedWrong owner or restrictive mode
EISDIRIs a directoryreadFile on a directory path
EMFILEToo many open filesFile descriptor leak
EEXISTFile already existsExclusive create conflict
$Blueprint — Engineering Documentation·Section ID: NODE-06·Revision: 1.0