File System Operations
One of NodeJS’s most powerful features is direct access to the file system. The built-in fs module lets you read, write, delete, and manipulate files and directories.
The fs Module
const fs = require('fs');
The fs module has two styles of APIs:
- Synchronous (blocking) - ends with
Sync - Asynchronous (non-blocking) - callback or promise-based
Reading Files
Synchronous (Blocking)
const fs = require('fs');
// Read entire file
const data = fs.readFileSync('data.txt', 'utf8');
console.log(data);
// Without 'utf8', returns a Buffer (binary data)
const buffer = fs.readFileSync('data.txt');
console.log(buffer); // <Buffer 48 65 6c 6c 6f>
console.log(buffer.toString()); // "Hello"
⚠️ Blocks execution until file is read - not recommended for servers.
Asynchronous (Callback)
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log(data);
});
console.log('This runs immediately!');
Asynchronous (Promises)
Modern and recommended approach:
const fs = require('fs').promises;
async function readMyFile() {
try {
const data = await fs.readFile('data.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('Error:', err);
}
}
readMyFile();
Or with .then():
fs.readFile('data.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
Writing Files
Write (Overwrite)
const fs = require('fs').promises;
async function writeFile() {
const content = 'Hello, World!';
await fs.writeFile('output.txt', content, 'utf8');
console.log('File written successfully');
}
writeFile();
Append (Add to End)
async function appendFile() {
await fs.appendFile('log.txt', 'New log entry\n', 'utf8');
console.log('Content appended');
}
Write JSON
async function writeJSON() {
const data = {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
};
const json = JSON.stringify(data, null, 2);
await fs.writeFile('users.json', json, 'utf8');
}
Read JSON
async function readJSON() {
const content = await fs.readFile('users.json', 'utf8');
const data = JSON.parse(content);
console.log(data.users[0].name); // "Alice"
}
Checking Files Exist
const fs = require('fs').promises;
async function checkFile(filepath) {
try {
await fs.access(filepath);
console.log('File exists');
return true;
} catch {
console.log('File does not exist');
return false;
}
}
checkFile('data.txt');
File Stats (Metadata)
async function getFileInfo(filepath) {
const stats = await fs.stat(filepath);
console.log('Size:', stats.size, 'bytes');
console.log('Created:', stats.birthtime);
console.log('Modified:', stats.mtime);
console.log('Is file:', stats.isFile());
console.log('Is directory:', stats.isDirectory());
}
getFileInfo('data.txt');
Deleting Files
async function deleteFile(filepath) {
try {
await fs.unlink(filepath);
console.log('File deleted');
} catch (err) {
console.error('Error deleting file:', err);
}
}
deleteFile('temp.txt');
Working with Directories
Create Directory
async function createDir() {
// Create single directory
await fs.mkdir('uploads');
// Create nested directories
await fs.mkdir('data/users/profiles', { recursive: true });
console.log('Directories created');
}
Read Directory
async function listFiles(dirpath) {
const files = await fs.readdir(dirpath);
console.log('Files in directory:');
files.forEach(file => console.log(file));
}
listFiles('.');
Read Directory with Details
async function listFilesDetailed(dirpath) {
const entries = await fs.readdir(dirpath, { withFileTypes: true });
for (const entry of entries) {
const type = entry.isDirectory() ? 'DIR ' : 'FILE';
console.log(`${type} ${entry.name}`);
}
}
listFilesDetailed('.');
Delete Directory
async function deleteDir(dirpath) {
// Delete empty directory
await fs.rmdir(dirpath);
// Delete directory and contents (be careful!)
await fs.rm(dirpath, { recursive: true, force: true });
console.log('Directory deleted');
}
Path Module
Work with file paths correctly across operating systems:
const path = require('path');
// Join path segments
const filepath = path.join('data', 'users', 'profile.json');
// Windows: data\users\profile.json
// Mac/Linux: data/users/profile.json
// Get absolute path
const absolute = path.resolve('data', 'users.json');
// /Users/you/project/data/users.json
// Get filename
path.basename('/data/users/alice.json'); // "alice.json"
// Get extension
path.extname('document.pdf'); // ".pdf"
// Get directory
path.dirname('/data/users/alice.json'); // "/data/users"
// Parse path
const parsed = path.parse('/data/users/alice.json');
// {
// root: '/',
// dir: '/data/users',
// base: 'alice.json',
// ext: '.json',
// name: 'alice'
// }
// Current file's directory (always use this for relative paths)
const dataPath = path.join(__dirname, 'data', 'users.json');
Real-World Examples
Log File Manager
const fs = require('fs').promises;
const path = require('path');
class Logger {
constructor(logDir = 'logs') {
this.logDir = logDir;
}
async init() {
await fs.mkdir(this.logDir, { recursive: true });
}
async log(message, level = 'INFO') {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${level}: ${message}\n`;
const date = new Date().toISOString().split('T')[0];
const logFile = path.join(this.logDir, `${date}.log`);
await fs.appendFile(logFile, logEntry);
}
async getRecentLogs(days = 7) {
const files = await fs.readdir(this.logDir);
const logFiles = files.filter(f => f.endsWith('.log'));
const logs = [];
for (const file of logFiles.slice(-days)) {
const content = await fs.readFile(
path.join(this.logDir, file),
'utf8'
);
logs.push({ file, content });
}
return logs;
}
}
// Usage
const logger = new Logger();
await logger.init();
await logger.log('Application started');
await logger.log('Database connected');
Config File Manager
const fs = require('fs').promises;
const path = require('path');
class Config {
constructor(filename = 'config.json') {
this.filepath = path.join(__dirname, filename);
this.data = {};
}
async load() {
try {
const content = await fs.readFile(this.filepath, 'utf8');
this.data = JSON.parse(content);
} catch (err) {
// File doesn't exist, use defaults
this.data = {};
}
}
async save() {
const json = JSON.stringify(this.data, null, 2);
await fs.writeFile(this.filepath, json, 'utf8');
}
get(key, defaultValue = null) {
return this.data[key] ?? defaultValue;
}
set(key, value) {
this.data[key] = value;
}
}
// Usage
const config = new Config();
await config.load();
config.set('apiUrl', 'https://api.example.com');
config.set('timeout', 5000);
await config.save();
File Processing Pipeline
const fs = require('fs').promises;
const path = require('path');
async function processTextFiles(inputDir, outputDir) {
// Ensure output directory exists
await fs.mkdir(outputDir, { recursive: true });
// Read all files
const files = await fs.readdir(inputDir);
const textFiles = files.filter(f => f.endsWith('.txt'));
// Process each file
for (const filename of textFiles) {
const inputPath = path.join(inputDir, filename);
const outputPath = path.join(outputDir, filename);
// Read content
const content = await fs.readFile(inputPath, 'utf8');
// Process (example: convert to uppercase)
const processed = content.toUpperCase();
// Write result
await fs.writeFile(outputPath, processed, 'utf8');
console.log(`Processed: ${filename}`);
}
console.log(`Processed ${textFiles.length} files`);
}
processTextFiles('./input', './output');
Stream for Large Files
For very large files, use streams to avoid loading entire file into memory:
const fs = require('fs');
// Create read stream
const readStream = fs.createReadStream('large-file.txt', 'utf8');
readStream.on('data', (chunk) => {
console.log('Received chunk:', chunk.length, 'bytes');
});
readStream.on('end', () => {
console.log('Finished reading');
});
readStream.on('error', (err) => {
console.error('Error:', err);
});
Best Practices
✅ DO:
- Use promises over callbacks for async operations
- Use
path.join()for cross-platform paths - Use
__dirnamefor relative paths - Handle errors with try/catch
- Use streams for large files
- Validate file paths before operations
❌ DON’T:
- Use synchronous methods in server code
- Hardcode file paths
- Forget to handle errors
- Load huge files into memory
- Trust user-provided file paths (security!)
Summary
| Operation | Async | Sync |
|---|---|---|
| Read file | fs.readFile() |
fs.readFileSync() |
| Write file | fs.writeFile() |
fs.writeFileSync() |
| Append | fs.appendFile() |
fs.appendFileSync() |
| Delete file | fs.unlink() |
fs.unlinkSync() |
| Check exists | fs.access() |
fs.accessSync() |
| Get stats | fs.stat() |
fs.statSync() |
| Read directory | fs.readdir() |
fs.readdirSync() |
| Make directory | fs.mkdir() |
fs.mkdirSync() |
Next Article: Creating HTTP Servers