Back to Blog
Node.jsFile SystemfsBackend

Node.js fs Module Guide

Master the Node.js file system module for reading, writing, and managing files.

B
Bootspring Team
Engineering
November 12, 2018
6 min read

The fs module provides APIs for interacting with the file system. Here's how to use it effectively.

Import Methods#

1// Promise-based API (recommended) 2import { readFile, writeFile } from 'node:fs/promises'; 3 4// Callback-based API 5import { readFile, writeFile } from 'node:fs'; 6 7// Synchronous API 8import { readFileSync, writeFileSync } from 'node:fs'; 9 10// Full module 11import * as fs from 'node:fs/promises';

Reading Files#

1import { readFile } from 'node:fs/promises'; 2 3// Read text file 4const content = await readFile('file.txt', 'utf8'); 5console.log(content); 6 7// Read as buffer 8const buffer = await readFile('image.png'); 9console.log(buffer.length, 'bytes'); 10 11// With error handling 12try { 13 const data = await readFile('config.json', 'utf8'); 14 const config = JSON.parse(data); 15} catch (error) { 16 if (error.code === 'ENOENT') { 17 console.log('File not found'); 18 } else { 19 throw error; 20 } 21} 22 23// Read JSON directly 24import { readFile } from 'node:fs/promises'; 25 26async function readJson(path) { 27 const content = await readFile(path, 'utf8'); 28 return JSON.parse(content); 29}

Writing Files#

1import { writeFile, appendFile } from 'node:fs/promises'; 2 3// Write text file 4await writeFile('output.txt', 'Hello, World!', 'utf8'); 5 6// Write JSON 7const data = { name: 'John', age: 30 }; 8await writeFile('data.json', JSON.stringify(data, null, 2)); 9 10// Append to file 11await appendFile('log.txt', `${new Date().toISOString()} - Event\n`); 12 13// Write with options 14await writeFile('file.txt', content, { 15 encoding: 'utf8', 16 mode: 0o644, 17 flag: 'w', // 'a' for append, 'wx' for exclusive write 18}); 19 20// Write buffer 21const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); 22await writeFile('binary.bin', buffer);

Directory Operations#

1import { mkdir, readdir, rmdir, rm } from 'node:fs/promises'; 2 3// Create directory 4await mkdir('new-folder'); 5 6// Create nested directories 7await mkdir('path/to/nested/folder', { recursive: true }); 8 9// Read directory 10const files = await readdir('src'); 11console.log(files); // ['index.js', 'utils.js', ...] 12 13// Read with file types 14const entries = await readdir('src', { withFileTypes: true }); 15for (const entry of entries) { 16 if (entry.isDirectory()) { 17 console.log('Dir:', entry.name); 18 } else if (entry.isFile()) { 19 console.log('File:', entry.name); 20 } 21} 22 23// Remove empty directory 24await rmdir('empty-folder'); 25 26// Remove directory recursively 27await rm('folder', { recursive: true, force: true });

File Information#

1import { stat, lstat, access, constants } from 'node:fs/promises'; 2 3// Get file stats 4const stats = await stat('file.txt'); 5console.log({ 6 size: stats.size, 7 isFile: stats.isFile(), 8 isDirectory: stats.isDirectory(), 9 created: stats.birthtime, 10 modified: stats.mtime, 11 permissions: stats.mode, 12}); 13 14// Check if path exists 15async function exists(path) { 16 try { 17 await access(path); 18 return true; 19 } catch { 20 return false; 21 } 22} 23 24// Check permissions 25async function canWrite(path) { 26 try { 27 await access(path, constants.W_OK); 28 return true; 29 } catch { 30 return false; 31 } 32} 33 34// lstat doesn't follow symlinks 35const linkStats = await lstat('symlink'); 36console.log(linkStats.isSymbolicLink());

Copy, Move, Rename#

1import { copyFile, rename, cp } from 'node:fs/promises'; 2 3// Copy file 4await copyFile('source.txt', 'destination.txt'); 5 6// Copy with flags 7import { constants } from 'node:fs'; 8await copyFile('src.txt', 'dest.txt', constants.COPYFILE_EXCL); 9 10// Rename/move file 11await rename('old-name.txt', 'new-name.txt'); 12await rename('file.txt', 'other-folder/file.txt'); 13 14// Copy directory recursively (Node 16.7+) 15await cp('src-folder', 'dest-folder', { recursive: true });

Streams#

1import { createReadStream, createWriteStream } from 'node:fs'; 2import { pipeline } from 'node:stream/promises'; 3 4// Read large file with stream 5const readStream = createReadStream('large-file.txt', 'utf8'); 6 7for await (const chunk of readStream) { 8 console.log('Chunk:', chunk.length); 9} 10 11// Write with stream 12const writeStream = createWriteStream('output.txt'); 13writeStream.write('Line 1\n'); 14writeStream.write('Line 2\n'); 15writeStream.end(); 16 17// Copy with streams (memory efficient) 18await pipeline( 19 createReadStream('source.txt'), 20 createWriteStream('destination.txt') 21); 22 23// Process large file line by line 24import { createInterface } from 'node:readline'; 25 26const rl = createInterface({ 27 input: createReadStream('large-file.txt'), 28 crlfDelay: Infinity, 29}); 30 31for await (const line of rl) { 32 console.log(line); 33}

Watching Files#

1import { watch } from 'node:fs/promises'; 2 3// Watch for changes 4const watcher = watch('src', { recursive: true }); 5 6for await (const event of watcher) { 7 console.log(`${event.eventType}: ${event.filename}`); 8} 9 10// With AbortController 11const ac = new AbortController(); 12 13setTimeout(() => ac.abort(), 10000); // Stop after 10s 14 15try { 16 const watcher = watch('src', { signal: ac.signal }); 17 for await (const event of watcher) { 18 console.log(event); 19 } 20} catch (error) { 21 if (error.name === 'AbortError') { 22 console.log('Stopped watching'); 23 } 24}
1import { symlink, readlink, realpath } from 'node:fs/promises'; 2 3// Create symlink 4await symlink('target-file.txt', 'link-name.txt'); 5 6// Create directory symlink 7await symlink('target-dir', 'link-dir', 'dir'); 8 9// Read symlink target 10const target = await readlink('link-name.txt'); 11console.log('Points to:', target); 12 13// Get real path (resolve symlinks) 14const real = await realpath('link-name.txt'); 15console.log('Real path:', real);

Temporary Files#

1import { mkdtemp, writeFile, rm } from 'node:fs/promises'; 2import { tmpdir } from 'node:os'; 3import { join } from 'node:path'; 4 5// Create temp directory 6const tempDir = await mkdtemp(join(tmpdir(), 'myapp-')); 7console.log('Temp dir:', tempDir); 8 9try { 10 // Use temp directory 11 await writeFile(join(tempDir, 'data.txt'), 'temporary data'); 12 13 // Process files... 14} finally { 15 // Clean up 16 await rm(tempDir, { recursive: true }); 17}

File Handles#

1import { open } from 'node:fs/promises'; 2 3// Open file handle 4const handle = await open('file.txt', 'r+'); 5 6try { 7 // Read from specific position 8 const buffer = Buffer.alloc(100); 9 const { bytesRead } = await handle.read(buffer, 0, 100, 0); 10 11 // Write at specific position 12 await handle.write('Hello', 0); 13 14 // Get stats 15 const stats = await handle.stat(); 16 17 // Truncate 18 await handle.truncate(1000); 19 20 // Sync to disk 21 await handle.sync(); 22} finally { 23 await handle.close(); 24} 25 26// Using with syntax 27import { open } from 'node:fs/promises'; 28 29const file = await open('file.txt', 'r'); 30try { 31 const content = await file.readFile('utf8'); 32} finally { 33 await file.close(); 34}

Recursive Operations#

1import { readdir, stat, rm } from 'node:fs/promises'; 2import { join } from 'node:path'; 3 4// Get all files recursively 5async function* getFiles(dir) { 6 const entries = await readdir(dir, { withFileTypes: true }); 7 8 for (const entry of entries) { 9 const path = join(dir, entry.name); 10 if (entry.isDirectory()) { 11 yield* getFiles(path); 12 } else { 13 yield path; 14 } 15 } 16} 17 18// Usage 19for await (const file of getFiles('src')) { 20 console.log(file); 21} 22 23// Get directory size 24async function getDirSize(dir) { 25 let size = 0; 26 for await (const file of getFiles(dir)) { 27 const stats = await stat(file); 28 size += stats.size; 29 } 30 return size; 31}

Permissions#

1import { chmod, chown } from 'node:fs/promises'; 2 3// Change permissions 4await chmod('script.sh', 0o755); // rwxr-xr-x 5await chmod('secret.txt', 0o600); // rw------- 6 7// Change owner (requires privileges) 8await chown('file.txt', uid, gid); 9 10// Permission constants 11import { constants } from 'node:fs'; 12// constants.S_IRUSR - owner read 13// constants.S_IWUSR - owner write 14// constants.S_IXUSR - owner execute

Best Practices#

API Choice: ✓ Use promises API (fs/promises) ✓ Use streams for large files ✓ Avoid sync methods in servers ✓ Use file handles for complex ops Error Handling: ✓ Check error.code (ENOENT, etc.) ✓ Handle EACCES (permission denied) ✓ Use try/finally for cleanup ✓ Validate paths before operations Performance: ✓ Stream large files ✓ Use recursive: true for dirs ✓ Batch operations when possible ✓ Close handles properly Security: ✓ Validate user input paths ✓ Use path.join for paths ✓ Set appropriate permissions ✓ Avoid path traversal attacks

Conclusion#

The fs module provides comprehensive file system operations. Use the promises API for modern async code, streams for large files, and file handles for precise control. Always handle errors appropriately and clean up resources. For security, validate paths and set proper permissions.

Share this article

Help spread the word about Bootspring