Node.js File Writing Explained: Everything You Need to Know About fs.writeFileSync()

File operations are a fundamental part of many Node.js applications. Whether you’re creating configuration files, logging data, or generating reports, understanding how to write files efficiently is essential. The fs.writeFileSync()
method provides a straightforward way to handle synchronous file writing operations in Node.js.
This comprehensive guide covers everything you need to know about using fs.writeFileSync()
effectively, from basic usage to advanced techniques and best practices.
Key Takeaways
fs.writeFileSync()
writes data to files synchronously, blocking the event loop until completion- Always use try-catch blocks to handle potential errors
- Use appropriate flags to control file creation and writing behavior
- Consider performance implications in high-concurrency applications
- For larger files or production environments, consider asynchronous alternatives
- Be mindful of security concerns, especially with user-provided paths
What is fs.writeFileSync()?
fs.writeFileSync()
is a built-in method in Node.js’s file system (fs
) module that writes data to a file synchronously. Unlike its asynchronous counterpart, this method blocks the execution of your code until the file operation completes.
The method signature is:
fs.writeFileSync(file, data[, options])
- file: Path to the file (string, Buffer, URL, or file descriptor)
- data: Content to write (string, Buffer, TypedArray, or DataView)
- options: Optional configuration parameters (string or object)
Basic Usage of fs.writeFileSync()
Writing a Simple Text File
const fs = require('fs');
try {
fs.writeFileSync('example.txt', 'Hello, Node.js!');
console.log('File written successfully');
} catch (err) {
console.error('Error writing file:', err);
}
This code creates a file named example.txt
with the content ""Hello, Node.js!"" in your current working directory.
Writing JSON Data
const fs = require('fs');
const user = {
name: 'John Doe',
age: 30,
email: 'john@example.com'
};
try {
fs.writeFileSync('user.json', JSON.stringify(user, null, 2));
console.log('JSON file written successfully');
} catch (err) {
console.error('Error writing JSON file:', err);
}
Understanding the Parameters
The File Parameter
The first parameter specifies the file path where data will be written:
// Writing to the current directory
fs.writeFileSync('data.txt', 'Some content');
// Writing to a specific path
fs.writeFileSync('/var/logs/app.log', 'Log entry');
// Using a file descriptor
const fd = fs.openSync('config.json', 'w');
fs.writeFileSync(fd, '{""setting"": ""value""}');
fs.closeSync(fd);
Important: If the specified directory doesn’t exist, Node.js will throw an error. The fs.writeFileSync()
method cannot create directories.
The Data Parameter
The second parameter contains the content you want to write:
// Writing a string
fs.writeFileSync('file.txt', 'Plain text content');
// Writing a Buffer
const buffer = Buffer.from('Binary content');
fs.writeFileSync('binary.dat', buffer);
// Writing a TypedArray
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // ""Hello"" in ASCII
fs.writeFileSync('typed-array.txt', uint8Array);
Working with Buffers
Buffers are particularly useful when dealing with binary data:
const fs = require('fs');
// Creating a buffer from a string
const stringData = 'Hello World';
const buffer = Buffer.from(stringData, 'utf8');
fs.writeFileSync('buffer-example.txt', buffer);
// Creating a buffer from JSON
const userData = { name: 'Alice', age: 28 };
const jsonBuffer = Buffer.from(JSON.stringify(userData));
fs.writeFileSync('user-data.json', jsonBuffer);
The Options Parameter
The third parameter allows you to customize the file writing behavior:
fs.writeFileSync('config.txt', 'Configuration data', {
encoding: 'utf8', // Character encoding (default: 'utf8')
mode: 0o666, // File permissions (default: 0o666)
flag: 'w' // File system flag (default: 'w')
});
Common Flag Options
'w'
: Open for writing, create if doesn’t exist, truncate if exists (default)'a'
: Open for appending, create if doesn’t exist'wx'
: Like ‘w’ but fails if the path exists'ax'
: Like ‘a’ but fails if the path exists'r+'
: Open for reading and writing, file must exist
// Append to a file instead of overwriting
fs.writeFileSync('log.txt', 'New log entryn', { flag: 'a' });
// Create a new file only if it doesn't exist
try {
fs.writeFileSync('config.json', '{}', { flag: 'wx' });
console.log('New config file created');
} catch (err) {
if (err.code === 'EEXIST') {
console.log('Config file already exists');
} else {
console.error('Error:', err);
}
}
Error Handling with fs.writeFileSync()
Since fs.writeFileSync()
is synchronous, errors are thrown directly. Always wrap it in a try-catch block:
try {
fs.writeFileSync('/path/to/file.txt', 'Content');
} catch (error) {
// Handle specific error types
if (error.code === 'ENOENT') {
console.error('Directory does not exist');
} else if (error.code === 'EACCES') {
console.error('Permission denied');
} else {
console.error('Unexpected error:', error);
}
}
Common Error Codes
ENOENT
: No such file or directory (often when parent directory doesn’t exist)EACCES
: Permission deniedEISDIR
: Is a directory (trying to write to a directory)EMFILE
: Too many open filesEEXIST
: File already exists (when using ‘wx’ or ‘ax’ flags)
Practical Use Cases
Creating Configuration Files
const fs = require('fs');
function createDefaultConfig() {
const config = {
apiKey: '',
debug: false,
logLevel: 'info',
maxRetries: 3
};
try {
fs.writeFileSync('config.json', JSON.stringify(config, null, 2));
console.log('Default configuration created');
} catch (err) {
console.error('Failed to create config file:', err);
}
}
// Check if config exists, create if not
try {
fs.accessSync('config.json', fs.constants.F_OK);
console.log('Config file already exists');
} catch (err) {
createDefaultConfig();
}
Simple Logging
function logMessage(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}n`;
try {
fs.writeFileSync('app.log', logEntry, { flag: 'a' });
} catch (err) {
console.error('Failed to write to log file:', err);
}
}
logMessage('Application started');
// Do something
logMessage('Operation completed');
Saving User Input
const readline = require('readline-sync');
const fs = require('fs');
function saveUserData() {
const username = readline.question('Enter username: ');
if (!username) {
console.log('Username cannot be empty');
return saveUserData();
}
try {
const userData = {
username,
createdAt: new Date().toISOString()
};
fs.writeFileSync(`${username}.json`, JSON.stringify(userData, null, 2));
console.log(`User data saved to ${username}.json`);
} catch (err) {
console.error('Failed to save user data:', err);
}
}
saveUserData();
Performance Considerations
Synchronous vs. Asynchronous Operations
fs.writeFileSync()
blocks the event loop until the operation completes, which can impact performance in high-concurrency applications:
// Synchronous (blocks the event loop)
console.time('writeFileSync');
fs.writeFileSync('large-file.txt', 'X'.repeat(1000000));
console.timeEnd('writeFileSync');
// Asynchronous (doesn't block the event loop)
console.time('writeFile');
fs.writeFile('large-file-async.txt', 'X'.repeat(1000000), () => {
console.timeEnd('writeFile');
});
console.log('This runs immediately while file is being written');
When to Use fs.writeFileSync()
Use fs.writeFileSync()
when:
- You’re writing small files
- You’re working in a script or CLI tool
- The file must be written before continuing execution
- You’re in a startup/initialization phase
Avoid fs.writeFileSync()
when:
- Working with large files
- In high-concurrency web servers
- In performance-critical code paths
- When handling multiple file operations simultaneously
Modern Alternatives
Using ES Modules Syntax
// ESM syntax (requires Node.js 12+)
import { writeFileSync } from 'fs';
import { join } from 'path';
const filePath = join(process.cwd(), 'data.txt');
writeFileSync(filePath, 'Content using ESM syntax');
Using the fs/promises API
For a more modern approach with promises while keeping synchronous behavior:
// Using fs/promises with top-level await (Node.js 14.8+ with ESM)
import { writeFile } from 'fs/promises';
try {
// This is still asynchronous but with cleaner syntax
await writeFile('example.txt', 'Content with promises');
console.log('File written successfully');
} catch (err) {
console.error('Error writing file:', err);
}
Security Considerations
File Permissions
Be mindful of file permissions when writing sensitive data:
// Set restrictive permissions for sensitive files
fs.writeFileSync('credentials.json', JSON.stringify(credentials), {
mode: 0o600 // Read/write for owner only
});
Path Traversal Vulnerabilities
Always validate and sanitize file paths, especially when they come from user input:
const path = require('path');
const fs = require('fs');
function safeWriteFile(filename, content) {
// Sanitize filename to prevent path traversal
const safeName = path.basename(filename);
const safePath = path.join('./uploads', safeName);
try {
fs.writeFileSync(safePath, content);
return true;
} catch (err) {
console.error('Error writing file:', err);
return false;
}
}
Troubleshooting Common Issues
""ENOENT: no such file or directory""
This usually means the directory doesn’t exist:
const fs = require('fs');
const path = require('path');
function writeFileWithDirectoryCreation(filePath, content) {
const directory = path.dirname(filePath);
try {
// Create directory if it doesn't exist
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
// Now write the file
fs.writeFileSync(filePath, content);
return true;
} catch (err) {
console.error('Error:', err);
return false;
}
}
writeFileWithDirectoryCreation('logs/app/data.log', 'Log entry');
""EACCES: permission denied""
Check file and directory permissions:
try {
fs.writeFileSync('/var/log/app.log', 'Log entry');
} catch (err) {
if (err.code === 'EACCES') {
// Try writing to a different location
const homeDir = require('os').homedir();
fs.writeFileSync(path.join(homeDir, 'app.log'), 'Log entry');
console.log('Wrote to home directory instead');
}
}
Conclusion
The fs.writeFileSync()
method is a powerful tool for handling file operations in Node.js applications. It provides a straightforward way to write data to files synchronously, making it ideal for scenarios where you need guaranteed completion before continuing execution. By understanding its parameters, error handling, and performance implications, you can effectively use this method in your projects while avoiding common pitfalls.
Remember to consider the synchronous nature of this method and use it appropriately based on your application’s needs. For high-concurrency applications or when working with large files, consider using asynchronous alternatives like fs.writeFile()
or streams.
FAQs
Use fs.writeFileSync() when you need guaranteed completion before continuing execution, typically in scripts, CLI tools, or initialization code. Use fs.writeFile() for most other scenarios, especially in servers or applications where performance matters.
Use the 'a' flag in the options parameter: fs.writeFileSync('log.txt', 'New entryn', { flag: 'a' });
No, it can only create files. You need to use fs.mkdirSync() to create directories first.
Use Buffer objects: const buffer = Buffer.from([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // 'Hello' fs.writeFileSync('binary.dat', buffer);
There's no specific limit in the API, but writing very large files synchronously can block the event loop for too long. For files larger than a few MB, consider using streams or asynchronous methods.
Set the mode option: fs.writeFileSync('sensitive.txt', 'private data', { mode: 0o600 });