Back to Blog
Node.jsFile SystemI/OStreams

Node.js File System Operations

Master Node.js file system module. From reading files to streaming to watching for changes.

B
Bootspring Team
Engineering
December 19, 2020
7 min read

The fs module provides comprehensive file system operations. Here's how to use it effectively.

Reading Files#

1const fs = require('fs'); 2const fsPromises = require('fs/promises'); 3 4// Synchronous (blocking) 5const data = fs.readFileSync('file.txt', 'utf8'); 6console.log(data); 7 8// Callback-based 9fs.readFile('file.txt', 'utf8', (err, data) => { 10 if (err) { 11 console.error('Error reading file:', err); 12 return; 13 } 14 console.log(data); 15}); 16 17// Promise-based (recommended) 18async function readFile() { 19 try { 20 const data = await fsPromises.readFile('file.txt', 'utf8'); 21 console.log(data); 22 } catch (err) { 23 console.error('Error reading file:', err); 24 } 25} 26 27// Read as buffer 28const buffer = await fsPromises.readFile('image.png'); 29console.log(buffer); // <Buffer ...> 30 31// Read JSON 32async function readJSON(path) { 33 const data = await fsPromises.readFile(path, 'utf8'); 34 return JSON.parse(data); 35}

Writing Files#

1const fsPromises = require('fs/promises'); 2 3// Write string 4await fsPromises.writeFile('output.txt', 'Hello, World!'); 5 6// Write with options 7await fsPromises.writeFile('output.txt', 'Hello', { 8 encoding: 'utf8', 9 mode: 0o644, 10 flag: 'w', 11}); 12 13// Flags: 14// 'w' - write (overwrite if exists) 15// 'a' - append 16// 'wx' - write, fail if exists 17// 'ax' - append, fail if exists 18 19// Append to file 20await fsPromises.appendFile('log.txt', 'New log entry\n'); 21 22// Write JSON 23async function writeJSON(path, data) { 24 await fsPromises.writeFile( 25 path, 26 JSON.stringify(data, null, 2), 27 'utf8' 28 ); 29} 30 31// Write buffer 32const buffer = Buffer.from('Hello, World!'); 33await fsPromises.writeFile('output.bin', buffer);

File Streams#

1const fs = require('fs'); 2const { pipeline } = require('stream/promises'); 3 4// Read stream 5const readStream = fs.createReadStream('large-file.txt', { 6 encoding: 'utf8', 7 highWaterMark: 64 * 1024, // 64KB chunks 8}); 9 10readStream.on('data', (chunk) => { 11 console.log('Received chunk:', chunk.length); 12}); 13 14readStream.on('end', () => { 15 console.log('Finished reading'); 16}); 17 18// Write stream 19const writeStream = fs.createWriteStream('output.txt'); 20 21writeStream.write('First line\n'); 22writeStream.write('Second line\n'); 23writeStream.end('Last line'); 24 25writeStream.on('finish', () => { 26 console.log('Finished writing'); 27}); 28 29// Pipe streams 30const read = fs.createReadStream('input.txt'); 31const write = fs.createWriteStream('output.txt'); 32 33read.pipe(write); 34 35// Using pipeline (handles errors) 36await pipeline( 37 fs.createReadStream('input.txt'), 38 fs.createWriteStream('output.txt') 39); 40 41// Transform while streaming 42const { Transform } = require('stream'); 43 44const uppercase = new Transform({ 45 transform(chunk, encoding, callback) { 46 callback(null, chunk.toString().toUpperCase()); 47 }, 48}); 49 50await pipeline( 51 fs.createReadStream('input.txt'), 52 uppercase, 53 fs.createWriteStream('output.txt') 54);

Directory Operations#

1const fsPromises = require('fs/promises'); 2const path = require('path'); 3 4// Create directory 5await fsPromises.mkdir('new-folder'); 6 7// Create nested directories 8await fsPromises.mkdir('path/to/nested/folder', { recursive: true }); 9 10// Read directory 11const files = await fsPromises.readdir('folder'); 12console.log(files); // ['file1.txt', 'file2.txt', 'subfolder'] 13 14// Read with file types 15const entries = await fsPromises.readdir('folder', { withFileTypes: true }); 16 17for (const entry of entries) { 18 if (entry.isDirectory()) { 19 console.log('Directory:', entry.name); 20 } else if (entry.isFile()) { 21 console.log('File:', entry.name); 22 } 23} 24 25// Remove directory 26await fsPromises.rmdir('empty-folder'); 27 28// Remove directory recursively 29await fsPromises.rm('folder', { recursive: true, force: true }); 30 31// List all files recursively 32async function* walkDir(dir) { 33 const entries = await fsPromises.readdir(dir, { withFileTypes: true }); 34 35 for (const entry of entries) { 36 const fullPath = path.join(dir, entry.name); 37 38 if (entry.isDirectory()) { 39 yield* walkDir(fullPath); 40 } else { 41 yield fullPath; 42 } 43 } 44} 45 46// Usage 47for await (const file of walkDir('./src')) { 48 console.log(file); 49}

File Information#

1const fsPromises = require('fs/promises'); 2 3// Get file stats 4const stats = await fsPromises.stat('file.txt'); 5 6console.log('Size:', stats.size); 7console.log('Created:', stats.birthtime); 8console.log('Modified:', stats.mtime); 9console.log('Is file:', stats.isFile()); 10console.log('Is directory:', stats.isDirectory()); 11console.log('Is symbolic link:', stats.isSymbolicLink()); 12 13// Check if file exists 14async function exists(path) { 15 try { 16 await fsPromises.access(path); 17 return true; 18 } catch { 19 return false; 20 } 21} 22 23// Check permissions 24const fs = require('fs'); 25const { constants } = fs; 26 27try { 28 await fsPromises.access( 29 'file.txt', 30 constants.R_OK | constants.W_OK 31 ); 32 console.log('File is readable and writable'); 33} catch { 34 console.log('No access'); 35} 36 37// Get real path (resolve symlinks) 38const realPath = await fsPromises.realpath('symlink');

File Operations#

1const fsPromises = require('fs/promises'); 2 3// Copy file 4await fsPromises.copyFile('source.txt', 'dest.txt'); 5 6// Copy without overwriting 7const { constants } = require('fs'); 8await fsPromises.copyFile( 9 'source.txt', 10 'dest.txt', 11 constants.COPYFILE_EXCL 12); 13 14// Rename/Move file 15await fsPromises.rename('old.txt', 'new.txt'); 16await fsPromises.rename('file.txt', 'folder/file.txt'); 17 18// Delete file 19await fsPromises.unlink('file.txt'); 20 21// Delete with force (no error if doesn't exist) 22await fsPromises.rm('file.txt', { force: true }); 23 24// Truncate file 25await fsPromises.truncate('file.txt', 100); // Keep first 100 bytes 26 27// Change permissions 28await fsPromises.chmod('file.txt', 0o755); 29 30// Change owner 31await fsPromises.chown('file.txt', uid, gid); 32 33// Create symbolic link 34await fsPromises.symlink('target.txt', 'link.txt'); 35 36// Create hard link 37await fsPromises.link('file.txt', 'hardlink.txt');

Watching Files#

1const fs = require('fs'); 2const fsPromises = require('fs/promises'); 3 4// Watch file for changes 5const watcher = fs.watch('file.txt', (eventType, filename) => { 6 console.log(`Event: ${eventType}, File: ${filename}`); 7}); 8 9// Watch directory 10fs.watch('folder', { recursive: true }, (eventType, filename) => { 11 console.log(`${eventType}: ${filename}`); 12}); 13 14// Clean up 15watcher.close(); 16 17// Using async iterator (Node.js 15.9+) 18async function watchForChanges() { 19 const watcher = fsPromises.watch('folder'); 20 21 for await (const event of watcher) { 22 console.log(event.eventType, event.filename); 23 } 24} 25 26// With debouncing (practical example) 27const debounce = require('lodash/debounce'); 28 29const handleChange = debounce((eventType, filename) => { 30 console.log(`${eventType}: ${filename}`); 31 // Rebuild, reload, etc. 32}, 100); 33 34fs.watch('src', { recursive: true }, handleChange);

Temporary Files#

1const fsPromises = require('fs/promises'); 2const os = require('os'); 3const path = require('path'); 4 5// Create temp directory 6const tempDir = await fsPromises.mkdtemp( 7 path.join(os.tmpdir(), 'myapp-') 8); 9console.log(tempDir); // /tmp/myapp-abc123 10 11// Create temp file 12async function createTempFile(content) { 13 const tempDir = await fsPromises.mkdtemp( 14 path.join(os.tmpdir(), 'myapp-') 15 ); 16 const tempFile = path.join(tempDir, 'temp.txt'); 17 18 await fsPromises.writeFile(tempFile, content); 19 20 return { 21 path: tempFile, 22 cleanup: async () => { 23 await fsPromises.rm(tempDir, { recursive: true }); 24 }, 25 }; 26} 27 28// Usage 29const temp = await createTempFile('temporary content'); 30console.log(temp.path); 31// ... use the file ... 32await temp.cleanup();

File Locking#

1const { open } = require('fs/promises'); 2 3// Advisory file locking 4async function withFileLock(path, fn) { 5 const lockPath = `${path}.lock`; 6 let lockHandle; 7 8 try { 9 // Create lock file (exclusive) 10 lockHandle = await open(lockPath, 'wx'); 11 12 // Execute function 13 return await fn(); 14 } finally { 15 if (lockHandle) { 16 await lockHandle.close(); 17 await unlink(lockPath); 18 } 19 } 20} 21 22// Usage 23await withFileLock('data.json', async () => { 24 const data = await readJSON('data.json'); 25 data.count++; 26 await writeJSON('data.json', data); 27}); 28 29// Using proper-lockfile package (recommended) 30const lockfile = require('proper-lockfile'); 31 32const release = await lockfile.lock('file.txt'); 33try { 34 // Work with file 35} finally { 36 await release(); 37}

Error Handling#

1const fsPromises = require('fs/promises'); 2 3// Common error codes 4// ENOENT - file not found 5// EACCES - permission denied 6// EEXIST - file already exists 7// EISDIR - is a directory 8// ENOTDIR - not a directory 9// ENOTEMPTY - directory not empty 10 11async function safeReadFile(path) { 12 try { 13 return await fsPromises.readFile(path, 'utf8'); 14 } catch (err) { 15 switch (err.code) { 16 case 'ENOENT': 17 console.error('File not found:', path); 18 return null; 19 case 'EACCES': 20 console.error('Permission denied:', path); 21 throw err; 22 default: 23 console.error('Unexpected error:', err); 24 throw err; 25 } 26 } 27} 28 29// Ensure directory exists 30async function ensureDir(dir) { 31 try { 32 await fsPromises.mkdir(dir, { recursive: true }); 33 } catch (err) { 34 if (err.code !== 'EEXIST') { 35 throw err; 36 } 37 } 38} 39 40// Safe write (atomic) 41async function safeWriteFile(path, content) { 42 const tempPath = `${path}.tmp`; 43 44 try { 45 await fsPromises.writeFile(tempPath, content); 46 await fsPromises.rename(tempPath, path); 47 } catch (err) { 48 // Clean up temp file 49 await fsPromises.unlink(tempPath).catch(() => {}); 50 throw err; 51 } 52}

Best Practices#

Performance: ✓ Use streams for large files ✓ Prefer async over sync methods ✓ Batch operations when possible ✓ Use proper highWaterMark Safety: ✓ Handle errors properly ✓ Check permissions before operations ✓ Use atomic writes for critical data ✓ Clean up temporary files Paths: ✓ Use path.join for cross-platform ✓ Resolve relative paths ✓ Sanitize user-provided paths ✓ Handle special characters Organization: ✓ Use fs/promises module ✓ Create helper functions ✓ Abstract file operations ✓ Test with mock file system

Conclusion#

Node.js provides comprehensive file system APIs. Use promise-based methods for modern code, streams for large files, and proper error handling. Always consider cross-platform compatibility and security when working with file paths.

Share this article

Help spread the word about Bootspring