04 - File System
📋 Jump to TakeawaysThis lesson uses ES Modules. Make sure your package.json has
"type": "module". See Modules if you need a refresher.
Reading Files
The fs module handles all file operations. Two flavors: callback-based and promise-based.
// Callback style (legacy)
import fs from 'node:fs';
fs.readFile('data.txt', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data);
});// Promise style (recommended)
import fs from 'node:fs/promises';
const data = await fs.readFile('data.txt', 'utf-8');
console.log(data);Always use fs/promises in new code. Callbacks are legacy.
In CommonJS projects, you'll need an async wrapper since top-level await isn't available:
const fs = require('fs/promises');
async function main() {
const data = await fs.readFile('data.txt', 'utf-8');
console.log(data);
}
main();Writing Files
import fs from 'node:fs/promises';
// Write (creates or overwrites)
await fs.writeFile('output.txt', 'Hello, Node!');
// Append (adds to end)
await fs.appendFile('log.txt', 'New entry\n');writeFile replaces the entire file. Use appendFile to add to it.
Checking if Files Exist
The simplest way is fs.existsSync(). It returns a boolean.
import fs from 'node:fs';
if (fs.existsSync('config.json')) {
console.log('File exists');
}For async code, use fs.access() with try-catch:
import fs from 'node:fs/promises';
try {
await fs.access('config.json');
console.log('File exists');
} catch {
console.log('File does not exist');
}- Don't use the deprecated
fs.exists(). - Use
existsSyncfor quick checks. - Use
fs.access()when you're inside async code like a server.
Working with Directories
import fs from 'node:fs/promises';
// Create a directory
await fs.mkdir('logs');
// Create nested directories
await fs.mkdir('data/backups/2026', { recursive: true });
// List directory contents
const files = await fs.readdir('.');
console.log(files); // ['index.js', 'package.json', 'node_modules']
// Remove a directory
await fs.rm('logs', { recursive: true });
// Remove directory with contents
await fs.rm('data', { recursive: true });File Info
import fs from 'node:fs/promises';
const stats = await fs.stat('index.js');
console.log(stats.isFile()); // true
console.log(stats.isDirectory()); // false
console.log(stats.size); // 1234 (bytes)The path Module
Never build file paths with string concatenation. Use path.
import path from 'node:path';
path.join('users', 'data', 'file.txt');
// "users/data/file.txt" (macOS/Linux)
// "users\\data\\file.txt" (Windows)
path.resolve('src', 'index.js');
// "/Users/you/project/src/index.js" (absolute path)
path.basename('/Users/you/project/app.js');
// "app.js"
path.extname('photo.png');
// ".png"
path.dirname('/Users/you/project/app.js');
// "/Users/you/project"path.join handles slashes across operating systems. path.resolve gives you an absolute path.
Wrap each statement in console.log to check the output.
Sync vs Async
Every fs method has a sync version. It blocks the entire process.
import fs from 'node:fs';
// Sync (blocking) - only for startup scripts
const data = fs.readFileSync('data.txt', 'utf-8');Sync methods are fine for CLI tools or one-time setup. Never use them in a server handling requests.
Key Takeaways
- Use
fs/promisesfor async file operations, not callbacks - High-level methods like
readFileandwriteFilehandle opening and closing files for you writeFileoverwrites,appendFileadds to the end- Use
existsSyncorfs.access()to check if a file exists, not the deprecatedfs.exists() mkdirwith{ recursive: true }creates nested directories- Always use
path.join()orpath.resolve()for file paths, never string concatenation - Sync methods block the process, only use them in scripts, never in servers