NodeJSAppending to Files

Appending to Files

Appending adds data to the end of a file instead of replacing it — the natural operation for logs, audit trails, and any file that grows over time. Node offers appendFile for one-off additions and write streams for high-frequency appends. The subtle part is doing it efficiently and safely when writes come fast.

appendFile

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

// Creates the file if missing, otherwise adds to the end:
await appendFile('app.log', `${new Date().toISOString()} started\n`)
await appendFile('app.log', 'another line\n')
`appendFile` creates the file if it doesn't exist
You don't need to check for or create the file first — `appendFile` opens it with the `'a'` flag, which creates-or-appends. The parent *directory*, however, must already exist, or you'll get `ENOENT`.
It's writeFile with a flag

Under the hood, appendFile(path, data) is exactly writeFile(path, data, { flag: 'a' }). The flag is what flips truncate-and-write into add-to-end:

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

await writeFile('x.txt', 'line\n', { flag: 'a' })   // same as appendFile
await appendFile('x.txt', 'line\n')                  // the idiomatic form
The hidden cost of frequent appends
Each `appendFile` opens, writes, and closes the file
Every `appendFile` call performs a full open → write → close cycle. That's fine occasionally, but calling it hundreds of times a second — e.g. per log line — is wasteful and can also interleave badly under heavy concurrency. For high-frequency appends, open the file **once** with a write stream and keep writing to it.

Efficient repeated appends — open once

JS
import { createWriteStream } from 'node:fs'

// flags: 'a' → append mode; the file handle stays open
const log = createWriteStream('app.log', { flags: 'a' })

log.write(`${Date.now()} request received\n`)
log.write(`${Date.now()} response sent\n`)
// …keep writing for the life of the process
process.on('exit', () => log.end())
A tiny logger

JS
import { createWriteStream } from 'node:fs'

function createLogger(path) {
  const stream = createWriteStream(path, { flags: 'a' })
  return {
    log: (msg) => stream.write(`${new Date().toISOString()} [INFO] ${msg}\n`),
    error: (msg) => stream.write(`${new Date().toISOString()} [ERROR] ${msg}\n`),
    close: () => stream.end(),
  }
}

const logger = createLogger('app.log')
logger.log('server started')
logger.error('connection dropped')
Concurrency and interleaving
Concurrent appends from multiple processes can interleave
A single `appendFile`/stream write smaller than the OS pipe buffer is generally atomic on most systems — but large writes, or multiple *processes* appending to the same file, can interleave and produce garbled lines. If several processes must log to one file, route through a single writer (a dedicated logging process, or a logging library that handles this), or give each process its own file.
Don't reinvent production logging
Use a logging library for real apps
Hand-rolled append-loggers are great for learning, but production needs log rotation (so files don't grow forever), levels, structured JSON, and async flushing. Libraries like `pino` and `winston` provide all of this and are highly optimized. Reserve raw `appendFile` for simple scripts and one-off audit trails.
When to use what

Situation

Use

Occasional append (a few times)

appendFile

Frequent appends in one process

createWriteStream({ flags: "a" })

Production application logging

A library (pino, winston)

Many processes, one log file

Single writer or per-process files

Next
Remove and rename files and clean up after yourself: [Deleting & Renaming Files](/nodejs/deleting-files).