NodeJSWriting Files

Writing Files

writeFile creates a file (or overwrites an existing one) with the data you give it. It's simple — but writing has sharp edges reading doesn't: it destroys existing content, it can be interrupted mid-write leaving a corrupt file, and large writes need streams. This page covers writing data correctly and safely.

Write or overwrite a file

JS
import { writeFile } from 'node:fs/promises'

await writeFile('out.txt', 'Hello, world\n')          // string
await writeFile('data.bin', Buffer.from([1, 2, 3]))    // Buffer
await writeFile('config.json', JSON.stringify(cfg, null, 2))  // JSON, pretty
`writeFile` REPLACES the whole file — silently
If the file exists, `writeFile` truncates it to zero and writes your data from scratch — the previous contents are gone with no warning. To *add* to a file instead, use `appendFile` (see [Appending to Files](/nodejs/appending-files)) or the `{ flag: 'a' }` option. To avoid clobbering an existing file, use `{ flag: 'wx' }`, which fails with `EEXIST` if it already exists.
Write flags

Flag

Behavior

'w' (default)

Create or truncate, then write

'wx'

Like 'w' but fail if the file exists

'a'

Create or append to the end

'ax'

Like 'a' but fail if the file exists

'r+'

Read/write without truncating (overwrite from the start)

JS
import { writeFile } from 'node:fs/promises'

// Fail rather than overwrite an existing file:
try {
  await writeFile('once.lock', 'locked', { flag: 'wx' })
} catch (err) {
  if (err.code === 'EEXIST') console.log('already exists — not overwriting')
}
Pretty-printing JSON

JS
import { writeFile } from 'node:fs/promises'

const data = { name: 'app', version: '1.0.0', tags: ['a', 'b'] }

// Third arg = indent spaces → human-readable, diff-friendly
await writeFile('app.json', JSON.stringify(data, null, 2) + '\n')
Add a trailing newline
Most tools and POSIX conventions expect text files to end with a newline. Appending `'\n'` keeps `git diff`, editors, and shell tools happy — and avoids the "No newline at end of file" warning.
The atomic-write pattern
A crash mid-write leaves a corrupt file
If the process dies (or the disk fills) partway through `writeFile`, you're left with a half-written, corrupt file — and you've already destroyed the good copy. For important files (config, state, anything you'd hate to lose), write to a temp file first, then `rename` it into place. `rename` is **atomic** on the same filesystem: readers see either the old file or the complete new one, never a partial.

Safe atomic write

JS
import { writeFile, rename } from 'node:fs/promises'

async function writeAtomic(path, data) {
  const tmp = `${path}.${process.pid}.tmp`
  await writeFile(tmp, data)     // write fully to a temp file
  await rename(tmp, path)         // atomic swap into place
}

await writeAtomic('state.json', JSON.stringify(state))
Ensure the directory exists
Writing to a missing directory throws ENOENT
`writeFile('logs/app.log', …)` fails with `ENOENT` if `logs/` doesn't exist — `writeFile` won't create parent directories. Create them first with `mkdir(dir, { recursive: true })`, which is a no-op if they already exist.

JS
import { writeFile, mkdir } from 'node:fs/promises'
import { dirname } from 'node:path'

async function writeSafe(path, data) {
  await mkdir(dirname(path), { recursive: true })   // ensure parent dirs
  await writeFile(path, data)
}
Large or streaming output

When data arrives over time or is too big to hold in memory, write through a stream — it applies backpressure so you never buffer more than necessary:

JS
import { createWriteStream } from 'node:fs'

const out = createWriteStream('big-export.csv')
out.write('id,name\n')
for (const row of rows) out.write(`${row.id},${row.name}\n`)
out.end()                                  // signal "no more data"
out.on('finish', () => console.log('written'))
out.on('error', (err) => console.error(err))

Writable streams and backpressure are covered in Writable Streams and Backpressure.

The rules
  • writeFile overwrites — use appendFile/flag: "a" to add, flag: "wx" to avoid clobbering.

  • Use the atomic temp-then-rename pattern for files you cannot afford to corrupt.

  • Create parent directories with mkdir({ recursive: true }) before writing.

  • For large/continuous output, use a write stream, not writeFile.

  • Always handle errors — disks fill, permissions deny, paths vanish.

Next
Make sense of which API style to use for all of this: [Sync vs Async vs Promises API](/nodejs/fs-sync-async).