Difficulty: Beginner
Writing files is the counterpart to reading and is equally essential in Node.js applications. Common use cases include writing log files, generating reports, saving user data, creating configuration files, and caching computed results. Like reading, Node.js provides callback-based, synchronous, and promise-based APIs for writing files.
fs.writeFile(path, data, options, callback) writes data to a file, creating the file if it does not exist or completely replacing its contents if it does. The data can be a string, Buffer, or TypedArray. Options include encoding (default 'utf8'), mode (file permissions), and flag (default 'w' for write). The promise-based equivalent is fsPromises.writeFile(). Always use the promise-based API with async/await in new code. Be careful: writeFile overwrites the entire file by default. If you want to add content to the end of a file, use appendFile instead.
fs.appendFile(path, data, options, callback) adds data to the end of a file without erasing existing content. If the file does not exist, it creates it. This is commonly used for log files where you want to add entries over time. However, for high-frequency appending (like logging many events per second), appendFile opens and closes the file for each call, which is inefficient. In such cases, use a write stream or a logging library.
File flags control how the file is opened. The default flag for writeFile is 'w' (open for writing, create if needed, truncate if exists). Other useful flags include 'a' (append), 'wx' (write but fail if file exists, useful for preventing accidental overwrites), 'r+' (open for reading and writing without truncating), and 'ax' (append but fail if exists). Understanding flags gives you fine-grained control over file operations.
For writing large amounts of data or writing continuously over time, use fs.createWriteStream(). A write stream buffers data and writes it in chunks, which is much more memory-efficient than building a large string and calling writeFile once. Write streams emit events like 'finish' (all data flushed), 'error', and 'drain' (ready for more data after backpressure). The stream approach is essential for generating large files like CSV exports, log files, or data dumps without consuming excessive memory.
const fs = require('fs/promises');
async function demo() {
// Write a new file (creates or overwrites)
await fs.writeFile('output.txt', 'Line 1\n', 'utf8');
console.log('File written');
// Append to the file
await fs.appendFile('output.txt', 'Line 2\n', 'utf8');
await fs.appendFile('output.txt', 'Line 3\n', 'utf8');
console.log('Lines appended');
// Read back to verify
const content = await fs.readFile('output.txt', 'utf8');
console.log('File contents:');
console.log(content);
// Write JSON data
const config = { host: 'localhost', port: 3000, debug: true };
await fs.writeFile(
'config.json',
JSON.stringify(config, null, 2),
'utf8'
);
console.log('JSON file written');
// Clean up
await fs.unlink('output.txt');
await fs.unlink('config.json');
}
demo().catch(console.error);
writeFile creates or overwrites the file. appendFile adds to the end without erasing. JSON.stringify with null and 2 as arguments produces pretty-printed JSON with 2-space indentation, making config files human-readable.
const fs = require('fs/promises');
async function safeWrite() {
const filename = 'important.txt';
// Write with 'wx' flag: fails if file already exists
try {
await fs.writeFile(filename, 'Original data', { flag: 'wx' });
console.log('File created successfully');
} catch (err) {
if (err.code === 'EEXIST') {
console.log('File already exists, not overwriting');
} else {
throw err;
}
}
// Try again (should fail since file now exists)
try {
await fs.writeFile(filename, 'New data', { flag: 'wx' });
} catch (err) {
console.log('Second write blocked:', err.code);
}
// Clean up
await fs.unlink(filename);
console.log('Cleanup done');
}
safeWrite();
The 'wx' flag stands for 'write exclusive'. It creates a new file but throws EEXIST if the file already exists. This is useful for operations where accidentally overwriting an existing file would cause data loss.
const fs = require('fs');
// Create a write stream
const stream = fs.createWriteStream('large-file.txt');
// Write 100 lines
for (let i = 1; i <= 100; i++) {
stream.write(`Line ${i}: This is some data\n`);
}
// Signal that we are done writing
stream.end();
// Events
stream.on('finish', () => {
console.log('All data written to file');
// Check file size
const stats = fs.statSync('large-file.txt');
console.log(`File size: ${stats.size} bytes`);
// Clean up
fs.unlinkSync('large-file.txt');
console.log('File deleted');
});
stream.on('error', (err) => {
console.error('Write error:', err.message);
});
createWriteStream is ideal for writing large amounts of data. It buffers writes internally and flushes them efficiently. The 'finish' event fires when all data has been flushed to the underlying file system. For very high throughput, monitor the 'drain' event to handle backpressure.
writeFile, appendFile, createWriteStream, File Flags, Atomic Writes