Write files in Node.js

Choose promise, callback, synchronous, or streaming file writes, and handle replacement, append mode, missing directories, and errors.

Best practices
Illustration accompanying a Node.js development guide

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.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting