File operations are fundamental to Node.js development. Whether you're creating configuration files, logging application events, or persisting data, understanding how to write files efficiently is essential. The fs.writeFileSync() method provides a straightforward way to handle synchronous file writing operations in your Node.js applications.
This comprehensive guide covers everything you need to know about using fs.writeFileSync() effectively, from basic syntax to advanced techniques and production best practices. For teams building web applications with Node.js, mastering file system operations is a critical skill that supports robust application architecture. Whether you're a beginner or an experienced developer, our web development services can help you build scalable, high-performance applications.
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
fs.writeFileSync(file, data[, options])
Key Characteristics
- Synchronous execution: The event loop is blocked until the file operation completes
- Automatic file creation: Creates the file if it doesn't exist
- Default truncation: Overwrites existing file content by default
- No return value: The method returns
undefinedon success
When to Use writeFileSync
fs.writeFileSync() is ideal for:
- Startup scripts and initialization code where blocking is acceptable
- CLI tools and build scripts that run once and complete
- Testing utilities where synchronous operations simplify test flow
- Configuration file management during application setup
For server applications handling concurrent requests, the asynchronous fs.writeFile() is generally preferred to avoid blocking the event loop. Understanding this distinction is essential for building scalable web applications with Node.js.
Understanding the Parameters
1. 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 writeFileSync() method cannot create parent directories.
2. 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 JSON (converted to string automatically)
const user = { name: 'John', age: 30 };
fs.writeFileSync('user.json', JSON.stringify(user));
3. 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
| Flag | Description | Creates File | Truncates |
|---|---|---|---|
'w' | Open for writing, truncate if exists | Yes | Yes (default) |
'a' | Open for appending to end | Yes | No |
'wx' | Like 'w' but fails if path exists | Yes | Yes |
'ax' | Like 'a' but fails if path exists | Yes | No |
'r+' | Open for reading and writing | No | No |
'a+' | Open for reading and appending | Yes | No |
Basic Usage Examples
Writing Text Files
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);
}
Writing JSON Files
const fs = require('fs');
const user = {
name: 'John Doe',
email: '[email protected]',
preferences: {
theme: 'dark',
notifications: true
}
};
try {
// Write with 2-space indentation for readability
fs.writeFileSync('user.json', JSON.stringify(user, null, 2));
console.log('JSON file written successfully');
// Verify by reading back
const savedData = JSON.parse(fs.readFileSync('user.json', 'utf8'));
console.log('User name:', savedData.name);
} catch (err) {
console.error('Error writing JSON file:', err);
}
Writing 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);
Using File Flags
// Append to a file instead of overwriting
fs.writeFileSync('log.txt', 'New log entry\n', { 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
Since fs.writeFileSync() is synchronous, errors are thrown directly and must be caught with try-catch blocks.
Try-Catch Pattern
const fs = require('fs');
try {
fs.writeFileSync('/path/to/file.txt', 'Content');
console.log('File written successfully');
} catch (error) {
// Handle specific error types
if (error.code === 'ENOENT') {
console.error('Directory does not exist. Create it first.');
} else if (error.code === 'EACCES') {
console.error('Permission denied. Check file permissions.');
} else if (error.code === 'EISDIR') {
console.error('Path is a directory, not a file.');
} else if (error.code === 'EEXIST') {
console.error('File already exists (when using \'wx\' or \'ax\' flags).');
} else {
console.error('Unexpected error:', error);
}
}
Common Error Codes
| Error Code | Meaning | Solution |
|---|---|---|
ENOENT | No such file or directory | Create parent directories with fs.mkdirSync() |
EACCES | Permission denied | Check file permissions or run with appropriate access |
EISDIR | Is a directory | Use a file path instead of directory |
EMFILE | Too many open files | Close unused file descriptors |
EEXIST | File already exists | Use a different flag or check before writing |
Best Practices for Error Handling
const fs = require('fs');
const path = require('path');
function safeWriteFile(filePath, content) {
// Ensure parent directory exists
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
try {
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
return { success: true };
} catch (err) {
return { success: false, error: err.message };
}
}
// Usage
const result = safeWriteFile('/new/dir/file.txt', 'Content');
if (result.success) {
console.log('File written successfully');
} else {
console.error('Failed:', result.error);
}
Robust error handling is a hallmark of professional web development practices, ensuring applications remain reliable under varying conditions.
Practical Use Cases
Configuration File Management
const fs = require('fs');
const path = require('path');
function createDefaultConfig(configPath) {
const defaultConfig = {
apiKey: '',
debug: false,
logLevel: 'info',
maxRetries: 3,
database: {
host: 'localhost',
port: 5432
}
};
try {
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
console.log('Default configuration created at:', configPath);
} catch (err) {
console.error('Failed to create config file:', err);
}
}
// Check if config exists, create if not
const configPath = 'config.json';
try {
fs.accessSync(configPath, fs.constants.F_OK);
console.log('Config file already exists');
} catch (err) {
createDefaultConfig(configPath);
}
Simple Logging
const fs = require('fs');
function logMessage(message, logFile = 'app.log') {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}\n`;
try {
fs.writeFileSync(logFile, logEntry, { flag: 'a' });
} catch (err) {
console.error('Failed to write to log file:', err);
}
}
// Usage
logMessage('Application started');
logMessage('User logged in', 'activity.log');
logMessage('Operation completed', 'activity.log');
Data Export
const fs = require('fs');
function exportData(data, outputPath) {
try {
// Write as formatted JSON
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
console.log(`Data exported to ${outputPath}`);
// Get file size
const stats = fs.statSync(outputPath);
console.log(`File size: ${stats.size} bytes`);
} catch (err) {
console.error('Export failed:', err);
}
}
// Example: Export analytics data
const analyticsData = {
users: 1250,
pageViews: 45890,
revenue: 12500.50,
exportsAt: new Date().toISOString()
};
exportData(analyticsData, 'analytics-export.json');
Build Script Usage
const fs = require('fs');
// Example: Generating a build manifest during build process
function generateBuildManifest() {
const manifest = {
version: process.env.npm_package_version || '1.0.0',
timestamp: new Date().toISOString(),
build: process.env.BUILD_NUMBER || Date.now(),
environment: process.env.NODE_ENV || 'development'
};
fs.writeFileSync('build-manifest.json', JSON.stringify(manifest, null, 2));
console.log('Build manifest generated:', manifest);
}
generateBuildManifest();
Performance Considerations
Synchronous vs Asynchronous Operations
fs.writeFileSync() blocks the event loop until the file operation completes. This 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');
// Output: writeFileSync: ~50ms (varies by system)
// Asynchronous (doesn't block the event loop)
console.time('writeFile');
fs.writeFile('large-file-async.txt', 'X'.repeat(1000000), () => {
console.timeEnd('writeFile');
// Output: writeFile: ~5ms (immediate return)
});
When Performance Matters
Avoid writeFileSync() in these scenarios:
- Web servers handling concurrent requests
- Real-time applications requiring low latency
- Batch processing with many file operations
- Microservices with high throughput requirements
Choosing the Right Approach
| Scenario | Recommended Method | Reason |
|---|---|---|
| Server startup | writeFileSync() | Runs once, blocking acceptable |
| Request handler | writeFile() | Non-blocking, concurrent requests |
| CLI tool | writeFileSync() | Simple, sequential execution |
| Background job | writeFile() | Non-blocking, can batch operations |
| Log rotation | writeFile() or appendFileSync() | Frequent operations |
Best Practice: Async for Production Servers
const fs = require('fs/promises');
async function writeConfigAsync(configPath, config) {
try {
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
console.log('Config saved successfully');
} catch (err) {
console.error('Failed to save config:', err);
throw err;
}
}
// Usage in async/await context
async function initServer() {
await writeConfigAsync('server-config.json', serverSettings);
await startServer();
}
For applications requiring sophisticated automation and AI-driven workflows, understanding when to use synchronous versus asynchronous operations becomes critical for system performance. Explore our AI automation services to learn how intelligent systems can optimize your file handling and data processing workflows.
Best Practices for Production Code
1. Use Explicit Encoding
Always specify encoding explicitly, even when using the default utf8:
// Good: Explicit encoding
fs.writeFileSync('file.txt', content, { encoding: 'utf8' });
// Avoid: Implicit default
fs.writeFileSync('file.txt', content); // Less clear
2. Validate and Sanitize File Paths
Prevent directory traversal vulnerabilities:
const path = require('path');
function safeWriteFile(baseDir, filename, content) {
// Resolve and validate path
const safePath = path.resolve(baseDir, filename);
// Ensure path is within base directory
if (!safePath.startsWith(path.resolve(baseDir))) {
throw new Error('Invalid file path');
}
fs.writeFileSync(safePath, content);
}
// Usage
safeWriteFile('/app/data', '../../etc/passwd', 'malicious'); // Throws error
3. Handle Directory Creation
Ensure parent directories exist before writing:
const fs = require('fs');
const path = require('path');
function ensureDirAndWrite(filePath, content) {
const dir = path.dirname(filePath);
// Create directory recursively if it doesn't exist
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, content);
}
// Usage
ensureDirAndWrite('/new/project/data/file.json', JSON.stringify(data));
4. Choose the Right Flag
Use wx flag for safe file creation:
// Safe: Only create new file, fail if exists
try {
fs.writeFileSync('new-config.json', '{}', { flag: 'wx' });
console.log('New config created');
} catch (err) {
if (err.code === 'EEXIST') {
console.log('Config already exists, skipping creation');
}
}
5. Use Async Alternatives for Servers
For production servers, prefer the promise-based API:
const fs = require('fs/promises');
async function handleUserUpload(filePath, content) {
try {
await fs.writeFile(filePath, content);
return { success: true, path: filePath };
} catch (err) {
return { success: false, error: err.message };
}
}
6. Implement Error Recovery
Plan for failure scenarios:
function writeWithRetry(filePath, content, maxRetries = 3) {
let attempts = 0;
while (attempts < maxRetries) {
try {
fs.writeFileSync(filePath, content);
return { success: true };
} catch (err) {
attempts++;
if (attempts === maxRetries) {
console.error(`Failed after ${maxRetries} attempts:`, err);
return { success: false, error: err.message, attempts };
}
// Wait before retry (exponential backoff)
const delay = Math.pow(2, attempts) * 100;
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
}
}
}
Comparison with Alternatives
writeFileSync vs writeFile
| Aspect | writeFileSync | writeFile |
|---|---|---|
| Execution | Synchronous (blocking) | Asynchronous (non-blocking) |
| Error handling | try-catch | Callback or promise rejection |
| Return value | undefined | undefined (callback receives error) |
| Use case | Scripts, CLI tools | Servers, concurrent apps |
| Event loop | Blocks until complete | Returns immediately |
// Synchronous
try {
fs.writeFileSync('file.txt', content);
console.log('Done'); // Waits for write to complete
} catch (err) {
console.error(err);
}
// Asynchronous
fs.writeFile('file.txt', content, (err) => {
if (err) {
console.error(err);
return;
}
console.log('Done'); // Callback when complete
});
// Promise-based (recommended for modern Node.js)
const fs = require('fs/promises');
await fs.writeFile('file.txt', content);
console.log('Done');
writeFileSync vs appendFileSync
writeFileSync: Overwrites file content (default behavior)appendFileSync: Adds content to the end of the file
// writeFileSync - overwrites
fs.writeFileSync('log.txt', 'New content\n'); // File now contains only "New content"
// appendFileSync - adds to end
fs.appendFileSync('log.txt', 'Another line\n'); // File contains both lines
Modern Promise-Based APIs
Node.js provides promise-based APIs through fs/promises:
const fs = require('fs/promises');
async function writeConfig() {
// Clean async/await syntax
await fs.writeFile('config.json', JSON.stringify(config));
// Combine with other async operations
const [configData] = await Promise.all([
fs.readFile('config.json', 'utf8'),
fs.writeFile('cache.json', JSON.stringify(cache))
]);
return JSON.parse(configData);
}
Building modern web applications requires understanding both synchronous and asynchronous patterns. Our web development services can help you implement best practices for file handling and application architecture.
Conclusion and Recommendations
fs.writeFileSync() is a straightforward method for writing files synchronously in Node.js. Here's when to use it and how to use it effectively:
When to Use writeFileSync
- Build scripts and CLI tools where synchronous execution simplifies code flow
- Application initialization and startup configuration
- Testing utilities that benefit from predictable execution order
- One-time file operations that don't impact concurrent requests
When to Avoid writeFileSync
- Web servers handling concurrent requests (use async alternatives)
- High-throughput applications with frequent file operations
- Real-time systems requiring consistent low latency
Quick Reference
// Basic syntax
fs.writeFileSync(file, data[, options])
// Common options
{
encoding: 'utf8', // Character encoding
mode: 0o666, // File permissions
flag: 'w' // Write behavior
}
// Best practice wrapper
function writeFileSafe(filePath, content) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
}
Choose the right tool for your specific use case. For scripts and initialization code, writeFileSync() provides simplicity and clarity. For production servers handling concurrent operations, the asynchronous fs.writeFile() or promise-based fs/promises.writeFile() offer better performance and scalability.