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}Symbolic Links#
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 executeBest 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.