File System (fs)
Read, write, and manage files and directories with Node's built-in fs module
Overview
Key Points
- β’Prefer fs/promises with async/await for all new code β it is cleaner than callbacks and non-blocking unlike sync methods
- β’fs.readFile returns a Buffer by default; pass { encoding: "utf8" } or "utf8" as the second argument to get a string
- β’fs.writeFile creates or truncates; fs.appendFile adds to existing content
- β’fs.mkdir with { recursive: true } is the equivalent of mkdir -p β it creates parent directories and ignores "already exists" errors
- β’fs.stat and fs.access check whether a file exists and get metadata (size, mtime, isDirectory)
- β’Always handle errors β files may not exist, permissions may be denied, or the disk may be full
Examples
Reading a file with fs/promises (recommended async/await style)
const fs = require('fs/promises');
async function readConfig() {
try {
const content = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(content);
console.log('Config loaded:', config);
return config;
} catch (err) {
if (err.code === 'ENOENT') {
console.log('Config file not found, using defaults');
return {};
}
throw err; // re-throw unexpected errors
}
}
readConfig();err.code === "ENOENT" means "file not found" β always handle this case specifically when a missing file is a recoverable condition.
Writing, appending, and reading back a file
const fs = require('fs/promises');
async function run() {
// Write (creates or overwrites)
await fs.writeFile('output.txt', 'Line 1\n', 'utf8');
// Append to existing content
await fs.appendFile('output.txt', 'Line 2\n', 'utf8');
// Read back
const content = await fs.readFile('output.txt', 'utf8');
console.log(content);
// Line 1
// Line 2
}
run();Working with directories
const fs = require('fs/promises');
const path = require('path');
async function setup() {
// Create nested directory (like mkdir -p)
await fs.mkdir('dist/assets/images', { recursive: true });
// List directory contents
const entries = await fs.readdir('src', { withFileTypes: true });
for (const entry of entries) {
const type = entry.isDirectory() ? 'dir ' : 'file';
console.log(`${type}: ${entry.name}`);
}
}
setup();Checking file metadata with fs.stat
const fs = require('fs/promises');
async function fileInfo(filePath) {
try {
const stats = await fs.stat(filePath);
console.log('Size (bytes):', stats.size);
console.log('Is directory:', stats.isDirectory());
console.log('Last modified:', stats.mtime);
console.log('Is file:', stats.isFile());
} catch (err) {
if (err.code === 'ENOENT') console.log('File not found');
else throw err;
}
}
fileInfo('package.json');Common Mistakes
- Using fs.readFileSync or fs.writeFileSync inside HTTP request handlers β these block the event loop and kill server throughput
- Forgetting { encoding: "utf8" } on readFile and getting a Buffer instead of a string
- Not handling ENOENT specifically β crashing when a missing file is a normal recoverable condition
- Using fs.exists() which is deprecated β use fs.access() or wrap fs.stat() in a try/catch instead
Note
For reading JSON config files that are needed at startup, using the synchronous API is fine β the process is not yet handling requests so blocking is acceptable. But once your HTTP server is listening, always use the async API for any file I/O triggered by requests.
Exercises
Write Node.js code in your local terminal. Use the editor below to follow along, then mark each exercise as done once it works.
Completed: 0 / 6 exercises
Knowledge Check
Question 1 of 4