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.