write-file-atomic
Atomic, crash-safe file writes for Node.js — write to a temp file, then rename into place, never a half-written file.
Repository Health
Technical Analysis
write-file-atomic is a drop-in replacement for Node’s fs.writeFile that makes the operation atomic. Instead of writing directly to the destination path, it writes the data to a uniquely-named temp file in the same directory, optionally copies over the original file’s ownership (uid/gid) and permissions, fsyncs the write, and then renames the temp file over the target. If any step fails, the temp file is unlinked and the error is passed back to the caller — the original file is never left partially written or corrupted.
Maintained by the npm CLI team, it is one of the most depended-upon low-level file I/O primitives in the Node ecosystem, pulling over 100 million weekly downloads as a transitive dependency of npm itself and countless CLIs, config writers, and build tools that need to persist state to disk without risking corruption from crashes, concurrent writers, or interrupted processes.
What You Get
- An async writeFileAtomic(filename, data, [options], [callback]) function with the same call signature shape as fs.writeFile, plus full Promise/async-await support
- A synchronous writeFileAtomicSync(filename, data, [options]) variant for code paths that can’t go async
- Automatic preservation of the destination file’s existing mode and uid/gid unless explicitly overridden via the chown/mode options
- An fsync option (default true) to force the write to durable storage before the rename, and a tmpfileCreated hook for inspecting or acting on the temp file mid-write
- Built-in serialization of concurrent writes to the same absolute path, so parallel calls don’t race each other or corrupt the destination
- Automatic cleanup of the temp file via signal-exit’s onExit handler if the process is killed mid-write
Common Use Cases
- CLIs and package managers (npm itself) persisting lockfiles, caches, or config files that must never be left half-written after a crash
- Build tools and bundlers writing output artifacts where a truncated file would silently break a downstream step
- Any long-running Node service that periodically checkpoints state to disk and can’t tolerate a corrupted checkpoint after an unexpected restart
- Config-writing libraries that need to preserve the original file’s permissions and ownership across an update
Under The Hood
Architecture The entire library lives in a single module, lib/index.js, exporting writeFile, its sync counterpart writeFileSync, and two testing-only internals (_getTmpname, _cleanupOnExit). There are no internal layers — it’s a flat functional module built around one shared piece of state, an activeFiles map keyed by absolute path, whose per-key array acts as a lightweight promise-based queue so concurrent writes to the same file execute in order while writes to different files proceed in parallel. Each write follows the same linear pipeline: resolve the real path, join the per-file queue, stat the existing file to inherit mode/ownership when not explicitly given, open and write the temp file, optionally fsync, close, apply chown/chmod, and finally rename the temp file over the destination inside a try/finally that always unlinks the leftover temp file and advances the queue. Because the queue is the only synchronization primitive protecting a given path, it is the one abstraction the whole correctness guarantee rests on — removing or bypassing it would reintroduce the exact race the library exists to prevent.
Tech Stack Dependencies are minimal by design: only Node’s own fs, path, crypto, and util modules plus a single external dependency, signal-exit (^4.0.1), used to register a cleanup handler that unlinks the temp file if the process is killed mid-write. There is no build step or transpilation — it ships as plain CommonJS JavaScript with an engines field pinning it to current Node LTS lines. Testing runs on tap, linting on @npmcli/eslint-config, and CI/release automation is scaffolded by @npmcli/template-oss, giving it a standard GitHub Actions pipeline (ci.yml, codeql-analysis.yml, audit.yml) plus a release-please-driven release flow.
Code Quality The test suite (basic.js, concurrency.js, integration.js) mocks fs at the call level via tap’s t.mock, deliberately injecting failures at every step of the pipeline — open, write, fsync, chown, chmod, rename — to verify each failure path correctly cleans up the temp file and surfaces the original error rather than swallowing it. Platform-specific chown/chmod errors (ENOSYS, EINVAL, EPERM) are handled explicitly through an isChownErrOk helper rather than caught generically, showing deliberate attention to cross-platform correctness. CodeQL scanning and scheduled dependency audits are wired into CI. There are no TypeScript types, but the small, stable public surface limits the practical cost of that.
API Design The public API is intentionally tiny — one async function and one sync function, both mirroring fs.writeFile’s calling convention (filename, data, options, callback) so it functions as a near drop-in replacement, plus support for options as a bare encoding string and full Promise/async-await usage without a wrapper. The README documents every option (chown, mode, encoding, fsync, tmpfileCreated) inline with defaults, so getting started requires no more than swapping the require and, optionally, awaiting the call.
Used by 2 apps in this directory
Activepieces
Automation · AI Assistants
Open-source AI automation platform that converts 280+ workflow integrations into MCP servers for LLMs, with no-code builders and TypeScript extensibility.
SillyTavern
AI Assistants
The power-user LLM frontend that unifies dozens of AI backends with a rich scripting engine, immersive Visual Novel mode, and a thriving extension ecosystem.