Using writeFileSync in Node.js

A complete guide to synchronous file writing with practical examples, error handling patterns, and production best practices.

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 undefined on 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

FlagDescriptionCreates FileTruncates
'w'Open for writing, truncate if existsYesYes (default)
'a'Open for appending to endYesNo
'wx'Like 'w' but fails if path existsYesYes
'ax'Like 'a' but fails if path existsYesNo
'r+'Open for reading and writingNoNo
'a+'Open for reading and appendingYesNo

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 CodeMeaningSolution
ENOENTNo such file or directoryCreate parent directories with fs.mkdirSync()
EACCESPermission deniedCheck file permissions or run with appropriate access
EISDIRIs a directoryUse a file path instead of directory
EMFILEToo many open filesClose unused file descriptors
EEXISTFile already existsUse 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

ScenarioRecommended MethodReason
Server startupwriteFileSync()Runs once, blocking acceptable
Request handlerwriteFile()Non-blocking, concurrent requests
CLI toolwriteFileSync()Simple, sequential execution
Background jobwriteFile()Non-blocking, can batch operations
Log rotationwriteFile() 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

AspectwriteFileSyncwriteFile
ExecutionSynchronous (blocking)Asynchronous (non-blocking)
Error handlingtry-catchCallback or promise rejection
Return valueundefinedundefined (callback receives error)
Use caseScripts, CLI toolsServers, concurrent apps
Event loopBlocks until completeReturns 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.

Need Custom Web Development?

Our team builds high-performance web applications using modern technologies like Node.js, React, and Next.js.