Working with the File System in Node.js
Node.js comes with a powerful built-in module for working with the file system: the fs module. Whether you need to read configuration files, write logs, process uploaded files, or build entire file-based databases, the fs module has you covered.
In this tutorial, you’ll learn how to read files, write files, work with directories, and handle common file system operations using both callbacks and promises.
Reading Files
The simplest way to read a file is with fs.readFile. Here’s how to read a text file:
const fs = require('fs');
fs.readFile('config.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File contents:', data);
});
The callback receives the error first (Node.js convention), then the file contents. The second argument 'utf8' tells Node.js to decode the buffer as a UTF-8 string.
Reading Files with Promises
If you prefer async/await syntax, use fs/promises:
const fs = require('fs/promises');
async function readConfig() {
try {
const data = await fs.readFile('config.json', 'utf8');
const config = JSON.parse(data);
console.log('Config loaded:', config);
} catch (err) {
console.error('Error reading config:', err);
}
}
readConfig();
Synchronous Reading
For scripts where async doesn’t matter, use the synchronous versions:
const fs = require('fs');
const data = fs.readFileSync('config.txt', 'utf8');
console.log('Contents:', data);
Warning: Avoid synchronous file operations in production servers—they block the event loop and can cause performance issues.
Writing Files
Writing files works similarly with fs.writeFile:
const fs = require('fs/promises');
async function saveData() {
const data = { name: 'Alice', age: 30 };
await fs.writeFile('user.json', JSON.stringify(data, null, 2));
console.log('Data saved!');
}
saveData();
By default, writeFile overwrites the file. To append instead:
const fs = require('fs/promises');
async function appendToLog(message) {
const timestamp = new Date().toISOString();
await fs.appendFile('app.log', `[${timestamp}] ${message}\n`);
}
appendToLog('Application started');
Working with Directories
Create directories with fs.mkdir:
const fs = require('fs/promises');
async function setupProject() {
await fs.mkdir('src/utils', { recursive: true });
await fs.mkdir('src/components', { recursive: true });
console.log('Directories created!');
}
setupProject();
The recursive: true option prevents errors if the directory already exists.
List directory contents with fs.readdir:
const fs = require('fs/promises');
async function listFiles(dir) {
const files = await fs.readdir(dir);
console.log('Files:', files);
}
listFiles('./src');
Checking File Stats
Get file metadata with fs.stat:
const fs = require('fs/promises');
async function fileInfo(filepath) {
const stats = await fs.stat(filepath);
console.log('Is file:', stats.isFile());
console.log('Is directory:', stats.isDirectory());
console.log('Size:', stats.size, 'bytes');
console.log('Created:', stats.birthtime);
console.log('Modified:', stats.mtime);
}
fileInfo('config.json');
Deleting Files and Directories
Remove files with fs.unlink:
const fs = require('fs/promises');
async function cleanup() {
await fs.unlink('temp.txt');
console.log('File deleted');
}
cleanup();
Remove directories with fs.rmdir:
const fs = require('fs/promises');
async function removeDir(dir) {
await fs.rmdir(dir, { recursive: true });
console.log('Directory removed');
}
removeDir('old-folder');
Working with Paths
Always use the path module for cross-platform path handling:
const path = require('path');
const filePath = path.join(__dirname, 'config', 'settings.json');
console.log('Full path:', filePath);
const filename = path.basename('/home/user/documents/report.pdf');
console.log('Filename:', filename); // 'report.pdf'
const ext = path.extname('image.png');
console.log('Extension:', ext); // '.png'
Streams for Large Files
For large files, use streams to avoid loading everything into memory:
const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt', 'utf8');
const writeStream = fs.createWriteStream('copy.txt');
readStream.on('data', (chunk) => {
writeStream.write(chunk);
});
readStream.on('end', () => {
console.log('File copied!');
});
readStream.on('error', (err) => {
console.error('Error:', err);
});
Or use pipeline for cleaner handling:
const fs = require('fs');
const { pipeline } = require('stream/promises');
async function copyFile(src, dest) {
const readStream = fs.createReadStream(src);
const writeStream = fs.createWriteStream(dest);
await pipeline(readStream, writeStream);
console.log('Copy complete!');
}
copyFile('large-file.txt', 'copy.txt');
Summary
You’ve learned the fundamentals of working with Node.js file system:
- Read files with
fs.readFileorfs/promises - Write files with
fs.writeFileand append withfs.appendFile - Create directories with
fs.mkdir(userecursive: true) - List contents with
fs.readdir - Get metadata with
fs.stat - Delete with
fs.unlinkandfs.rmdir - Use streams for large files to avoid memory issues
The fs/promises API is recommended for modern Node.js applications—it works seamlessly with async/await and is easier to reason about.
In the next tutorial, you’ll learn how to build an HTTP server with Node.js.