fs is how Node.js reads, writes, and manages files and directories — one of the modules that made server-side JavaScript genuinely useful from day one.
Three ways to call the same operation
Most fs methods come in three flavors, and knowing which to reach for matters:
// Synchronous — blocks until done, simplest to read
const data = fs.readFileSync('data.txt', 'utf8');
console.log(data);
// Callback-based — the traditional non-blocking style
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) return console.error(err);
console.log(data);
});
// Promise-based — works cleanly with async/await
const fsPromises = require('fs/promises');
async function readIt() {
const data = await fsPromises.readFile('data.txt', 'utf8');
console.log(data);
}
For anything running while your server actively handles requests, favor the callback or Promise versions — as covered in the earlier blocking/non-blocking page, the synchronous version freezes the whole process until it finishes.
Writing and appending
const fsPromises = require('fs/promises');
await fsPromises.writeFile('log.txt', 'Server started\n'); // overwrites the file entirely
await fsPromises.appendFile('log.txt', 'User logged in\n'); // adds to the end instead
Checking whether a file exists before touching it
const fs = require('fs');
try {
await fsPromises.access('config.json', fs.constants.F_OK);
console.log('File exists');
} catch {
console.log('File does not exist');
}
Working with directories
await fsPromises.mkdir('uploads', { recursive: true }); // recursive: also creates parent folders as needed
const files = await fsPromises.readdir('uploads');
console.log(files); // array of filenames in that directory
const stats = await fsPromises.stat('uploads/photo.jpg');
console.log(stats.size, stats.isFile(), stats.isDirectory());
Deleting
await fsPromises.unlink('temp-file.txt'); // deletes one file
await fsPromises.rm('old-uploads', { recursive: true }); // deletes a folder and everything in it
A realistic small example — saving a JSON record
const fsPromises = require('fs/promises');
async function saveUser(user) {
const existing = JSON.parse(await fsPromises.readFile('users.json', 'utf8').catch(() => '[]'));
existing.push(user);
await fsPromises.writeFile('users.json', JSON.stringify(existing, null, 2));
}
saveUser({ id: 1, name: 'Nusrat' });
The .catch(() => '[]') handles the case where users.json doesn't exist yet, falling back to an empty array rather than crashing.
One important boundary worth knowing
For genuinely large files (video, large datasets), reading the whole thing into memory at once with readFile can be wasteful or even crash the process if the file is bigger than available memory. That's exactly the problem streams solve, covered on a later page — fs.createReadStream() and fs.createWriteStream() process a file in manageable pieces instead of loading it all at once.