Write files in Node.js
Choose promise, callback, synchronous, or streaming file writes, and handle replacement, append mode, missing directories, and errors.
For most application code, use writeFile from node:fs/promises and await completion. It creates a missing file or replaces an existing file by default. Parent directories must already exist.
Write a string or JSON document
Save this example as write.mjs:
import { mkdir, writeFile, appendFile } from 'node:fs/promises';
await mkdir('output', { recursive: true });
await writeFile('output/message.txt', 'Hello, Node.js!\n', 'utf8');
const settings = { enabled: true, retries: 3 };
await writeFile('output/settings.json', JSON.stringify(settings, null, 2) + '\n');
await appendFile('output/message.txt', 'Another line\n', 'utf8');
Each operation completes before the next begins. Avoid overlapping writes to the same path unless your application coordinates them. writeFile is not an atomic document-update protocol, and completion is not by itself a promise that data survives every power failure.
Strings use UTF-8 by default. Pass a Buffer when writing binary bytes; a text encoding does not reinterpret a Buffer's contents.
Callback and synchronous alternatives
These are independent alternatives to the promise example:
const fs = require('node:fs');
fs.writeFile('message.txt', 'Hello\n', 'utf8', (error) => {
if (error) {
console.error('Write failed:', error);
process.exitCode = 1;
return;
}
console.log('Write complete');
});
const fs = require('node:fs');
try {
fs.writeFileSync('message.txt', 'Hello\n', 'utf8');
} catch (error) {
console.error('Write failed:', error);
process.exitCode = 1;
}
Synchronous operations block the calling JavaScript thread. They can be reasonable in a short setup script, but repeated synchronous writes in a request handler delay other work on that thread.
Stream a large output
A stream avoids constructing one giant output string. pipeline coordinates backpressure, completion, and errors:
import { createWriteStream } from 'node:fs';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
function* rows() {
for (let index = 0; index < 100000; index++) {
yield `row ${index}\n`;
}
}
await pipeline(Readable.from(rows()), createWriteStream('rows.txt'));
If you call a writable stream's write() directly, respect a false return value and wait for drain, while also handling errors. Calling write() repeatedly without limits can still consume substantial memory. Modern writeFile also accepts iterable input, but streaming remains useful for composing data sources and transformations.
Common questions
How do I avoid replacement? Append with appendFile, or use the exclusive-create flag { flag: 'wx' } when an existing file should cause failure.
Why does writing fail? Check the parent path, permissions, available storage, and whether a path component is a directory. Handle the actual operation's error rather than assuming a prior existence check guarantees success.
Can I append an object? Serialize it deliberately. Appending several JSON objects does not create a valid single JSON document; newline-delimited JSON is a separate format.
See Node's file system API and stream pipeline.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan