File System Module (fs)
Thefs module is one of the most useful built-in modules in Node.js. It allows you to work with the file system on your computer: reading files, creating files, updating files, deleting files, and renaming files.
To use it, you must first require it:
Synchronous vs Asynchronous
Most methods in thefs module have both synchronous and asynchronous versions.
- Asynchronous methods take a callback function as the last argument. They are non-blocking.
- Synchronous methods block the execution until the operation completes. They usually end with
Sync.
fs API Style Comparison
Node.js offers three ways to do file operations. Here is how they compare:
Decision framework: Use
fs/promises by default in any new code. Use synchronous methods only in two situations: (1) reading config at process startup before the server starts accepting connections, or (2) CLI scripts where blocking is acceptable. Use callback-style only when integrating with legacy callback-based code that you cannot refactor.
Reading Files
Asynchronous Read
<Buffer 48 65 6c 6c 6f> instead of readable text.
Synchronous Read
Writing Files
Asynchronous Write
fs.writeFile() replaces the file and content if it exists. If the file doesn’t exist, a new file, containing the specified content, will be created.
Appending to Files
To add content to the end of a file without replacing it, usefs.appendFile().
Directories
Creating a Directory
Reading a Directory
Removing Files and Directories
Promise-based API (fs/promises)
Modern Node.js provides a promise-based API that works beautifully with async/await.Watching Files for Changes
Working with JSON Files
Practical Example: File-based Logger
Performance: readFile vs Streams vs readline
Choosing the right file-reading approach depends on file size and what you need to do with the data:
Benchmark intuition (rough numbers on modern hardware):
readFileSyncon a 1MB file: ~2msreadFileSyncon a 100MB file: ~80ms (and blocks the event loop the entire time)createReadStreamon a 100MB file: ~80ms total, but the event loop stays responsive throughoutreadFileSyncon a 1GB file: ~800ms of blocked event loop — every request to your server waits
readFile on the same 10MB file simultaneously, you will use ~1GB of RAM (100 copies of the file). Node.js does not deduplicate concurrent reads of the same file. For frequently-accessed files, read once at startup and cache the result, or use a stream that pipes directly to the response.
Common Pitfalls with File Operations
Common fs Error Codes
When file operations fail, the error object includes acode property. Knowing these codes lets you handle failures precisely:
Edge case — EMFILE in production: The default file descriptor limit on many Linux systems is 1024. A busy server that opens files for logging, uploads, and static assets can hit this limit. Symptoms: random “EMFILE: too many open files” errors that seem intermittent because they depend on how many files are open at that moment. Fix: increase the limit with
ulimit -n 65536 in your startup script, and ensure you always close file handles (streams auto-close on end/error, but manual fs.open calls need explicit fs.close).
Summary
- Use asynchronous methods in production (avoid
Syncmethods except at startup) - Prefer
fs/promiseswith async/await for modern code—it eliminates callback nesting - Use
fs.watch()to monitor file/directory changes (but note it is platform-dependent and can fire duplicate events) fs.stat()provides file metadata (size, dates, type)- Always handle errors—file not found (
ENOENT), permission denied (EACCES), and disk full (ENOSPC) are the most common - For large files, use Streams instead of
readFile(covered in detail in Chapter 06) - Use
{ recursive: true }withfs.mkdirto safely create nested directories without checking existence first