The fs (file system) module is one of the most-used built-in modules in Node.js, powering everything from config loading to log writing. This lesson covers reading, writing, and managing files and directories.
What Is the fs Module?
fs gives JavaScript direct access to the file system, something browsers deliberately don't allow for security reasons. You can read and write files, create and remove directories, check file metadata, and watch for changes, all through one built-in module.
fs exposes three parallel API styles for most operations: synchronous (fs.readFileSync, blocks the event loop until done), callback-based (fs.readFile(path, cb), the original async style), and promise-based (fs.promises.readFile or node:fs/promises, designed for use with async/await).
import { readFile, writeFile } from 'node:fs/promises';
async function main() {
const data = await readFile('./notes.txt', 'utf8');
console.log('Current notes:', data);
await writeFile('./notes.txt', data + '\nNew line added.');
console.log('File updated.');
}
main();
The promise-based API (node:fs/promises) is the recommended default for new code, it pairs naturally with async/await and avoids callback nesting.
Sync methods end in Sync and block the whole event loop until they finish.
Callback methods are the original async style, error-first callback (err, data).
Promise methods live under fs.promises (or import from node:fs/promises) and work with await.
Prefer promise-based or callback-based methods for anything running inside a server.
fs Module Cheatsheet
The file system operations you'll reach for constantly.
Task
Promise API
Read a file
await readFile(path, 'utf8')
Write a file
await writeFile(path, data)
Append to a file
await appendFile(path, data)
Delete a file
await unlink(path)
Create a directory
await mkdir(path, { recursive: true })
List a directory
await readdir(path)
Check existence
await access(path) (throws if missing)
Get file info
await stat(path)
Sync vs Async: Why It Matters
fs.readFileSync() blocks the entire event loop while it runs, no other request, timer, or callback can execute in that Node.js process until the read finishes. That's usually fine for one-off scripts or reading config at startup, but disastrous inside a running web server, where it would freeze every connected client.
// OK at startup, config read once before the server starts
import { readFileSync } from 'node:fs';
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
// Avoid inside a request handler, it blocks every other request
app.get('/report', (req, res) => {
const data = readFileSync('./huge-report.csv', 'utf8'); // blocks!
res.send(data);
});
Working With Directories
Beyond individual files, fs also manages directories: creating nested folder structures, listing contents, and getting detailed metadata about whether a path is a file or a directory.
import { mkdir, readdir, stat } from 'node:fs/promises';
await mkdir('./uploads/2026/07', { recursive: true });
const entries = await readdir('./uploads');
for (const entry of entries) {
const info = await stat(`./uploads/${entry}`);
console.log(entry, info.isDirectory() ? 'directory' : 'file');
}
{ recursive: true } on mkdir creates every missing intermediate folder, similar to mkdir -p on the command line.
Common Mistakes
Using fs.readFileSync() inside route handlers of a running HTTP server, blocking all other requests.
Forgetting the 'utf8' encoding argument, which causes readFile to return a raw Buffer instead of a string.
Not handling ENOENT errors when a file might not exist, and letting the process crash unhandled.
Building file paths with string concatenation instead of the path module, breaking cross-platform compatibility.
Key Takeaways
The fs module provides sync, callback, and promise-based APIs for the same operations.
Sync methods block the entire event loop and should be avoided in running servers.
node:fs/promises pairs naturally with async/await and is the recommended default.
{ recursive: true } on mkdir creates nested directories in one call.
Pro Tip
Wrap file operations that might fail (missing files, permission errors) in try/catch and check error.code (like 'ENOENT') to handle specific failure cases gracefully instead of letting your process crash.
You now know how to read, write, and manage files with Node.js. Next, learn the path module for building file paths safely across operating systems.