Working with Files
One of Node.js's superpowers is file system access. Let's learn to read, write, and manage files.
The fs Module
const fs = require('fs');
This built-in module gives you full file system access.
Reading Files
Async (recommended):// read-async.js
const fs = require('fs');
fs.readFile('hello.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err.message);
return;
}
console.log('File contents:', data);
});
console.log('This prints while file is being read...');
Output:
This prints while file is being read...
File contents: Hello, World!
Sync (blocks everything):
// read-sync.js
const fs = require('fs');
try {
const data = fs.readFileSync('hello.txt', 'utf8');
console.log('File contents:', data);
} catch (err) {
console.error('Error:', err.message);
}
console.log('This prints after file is read');
Output:
File contents: Hello, World!
This prints after file is read
Writing Files
Create or overwrite a file:// write.js
const fs = require('fs');
const content = 'Hello, Node.js!\nThis is line 2.';
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
console.error('Error writing file:', err.message);
return;
}
console.log('File written successfully!');
});
Append to a file:
// append.js
const fs = require('fs');
fs.appendFile('log.txt', 'New log entry\n', (err) => {
if (err) {
console.error('Error:', err.message);
return;
}
console.log('Content appended!');
});
Checking if Files Exist
// exists.js
const fs = require('fs');
// Modern way - check access
fs.access('myfile.txt', fs.constants.F_OK, (err) => {
if (err) {
console.log('File does not exist');
} else {
console.log('File exists');
}
});
// Sync version
if (fs.existsSync('myfile.txt')) {
console.log('File exists');
}
Working with Directories
// directories.js
const fs = require('fs');
// Create a directory
fs.mkdir('new-folder', (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Error creating directory:', err.message);
return;
}
console.log('Directory created (or already exists)');
});
// Read directory contents
fs.readdir('.', (err, files) => {
if (err) {
console.error('Error:', err.message);
return;
}
console.log('Files in current directory:');
files.forEach(file => console.log(' -', file));
});
// Delete a file
fs.unlink('temp.txt', (err) => {
if (err) {
console.error('Error deleting:', err.message);
return;
}
console.log('File deleted');
});
The path Module
Always use path for file paths - it handles OS differences:
// path-demo.js
const path = require('path');
// Join paths safely
const filePath = path.join('users', 'alice', 'documents', 'file.txt');
console.log(filePath);
// Windows: users\alice\documents\file.txt
// Mac/Linux: users/alice/documents/file.txt
// Get parts of a path
const fullPath = '/home/user/projects/app.js';
console.log(path.dirname(fullPath)); // /home/user/projects
console.log(path.basename(fullPath)); // app.js
console.log(path.extname(fullPath)); // .js
// Get absolute path
console.log(path.resolve('file.txt'));
// /Users/you/current-directory/file.txt
// __dirname = directory of current file
console.log(__dirname);
// /Users/you/projects/my-app
// Combine with __dirname for reliable paths
const configPath = path.join(__dirname, 'config', 'settings.json');
Real Example: Simple Logger
// logger.js
const fs = require('fs');
const path = require('path');
const logFile = path.join(__dirname, 'app.log');
function log(message) {
const timestamp = new Date().toISOString();
const logLine = `[${timestamp}] ${message}\n`;
// Print to console
console.log(logLine.trim());
// Append to file
fs.appendFile(logFile, logLine, (err) => {
if (err) console.error('Failed to write log:', err.message);
});
}
// Usage
log('Application started');
log('User logged in');
log('Processing request...');
app.log contents:
[2024-01-15T10:30:00.000Z] Application started
[2024-01-15T10:30:01.000Z] User logged in
[2024-01-15T10:30:02.000Z] Processing request...
Try It Yourself
Create a simple note-taking CLI:
// notes.js
const fs = require('fs');
const path = require('path');
const notesFile = path.join(__dirname, 'notes.json');
// Load existing notes
function loadNotes() {
try {
const data = fs.readFileSync(notesFile, 'utf8');
return JSON.parse(data);
} catch (err) {
return []; // No notes file yet
}
}
// Save notes
function saveNotes(notes) {
fs.writeFileSync(notesFile, JSON.stringify(notes, null, 2));
}
// Get command and arguments
const command = process.argv[2];
const noteText = process.argv.slice(3).join(' ');
const notes = loadNotes();
switch (command) {
case 'add':
if (!noteText) {
console.log('Usage: node notes.js add <note text>');
break;
}
notes.push({ text: noteText, date: new Date().toISOString() });
saveNotes(notes);
console.log('Note added!');
break;
case 'list':
if (notes.length === 0) {
console.log('No notes yet. Add one with: node notes.js add <text>');
} else {
console.log('Your notes:');
notes.forEach((note, i) => {
console.log(`${i + 1}. ${note.text}`);
});
}
break;
case 'clear':
saveNotes([]);
console.log('All notes cleared!');
break;
default:
console.log('Commands: add, list, clear');
}
Run it:
node notes.js add Buy groceries
node notes.js add Call mom
node notes.js list
node notes.js clear
Output:
Note added!
Note added!
Your notes:
1. Buy groceries
2. Call mom
All notes cleared!
Key Takeaways
- fs module provides file system access
- Async methods (readFile, writeFile) are non-blocking
- Sync methods (readFileSync) block the event loop
- path module handles OS path differences
- __dirname gives you the current file's directory
- Always handle errors - files might not exist
Next, let's learn about async/await for cleaner asynchronous code!