Storage & Backups
Your database lives in server/data/ as a tree of data.json files. The server keeps the entire tree in memory and writes dirty subtrees back to disk on a 5-second debounce (or sooner under load). You don't normally need to touch these files — but it helps to know how they're laid out.
The split tree
To keep individual JSON files from growing unbounded, NukeBase splits any object that would exceed 128 MB serialized into a subdirectory. The parent file references the child via a $split marker:
server/data/
├── data.json # { "users": "$split", "posts": [...] }
└── users/
├── data.json # { "alice": {...}, "bob": "$split" }
└── bob/
└── data.json # { ...bob's subtree... }
Splits happen automatically when an object grows past the threshold, and the inverse — coalescing back into the parent file — happens automatically on startup if a child has shrunk under the threshold. Migration from a legacy single server/data.json file is also automatic on first run.
Don't hand-edit while the server is running. The in-memory tree is the source of truth; on the next flush, your edits will be overwritten. Stop the server first, edit the relevant data.json, then restart.
Atomic writes & crash safety
Each data.json is written via a tmp + fsync + rename sequence, so a crash mid-write leaves either the previous file or the new file on disk — never a partially-written one. Any leftover .tmp files from a crashed write are swept on startup before the data is loaded. Pending in-memory writes are flushed on SIGTERM/SIGINT for graceful shutdown.
Daily backup
Every 24 hours the server flushes pending writes and copies server/data/ to server/backup/<YYYY-MM-DD>/. Same-day re-runs overwrite the existing snapshot. No configuration is required.
Bad-disk recovery: If a $split subdirectory is missing or corrupt at startup, the server fails fast by default — it won't load partial data and silently re-flush, which would commit data loss. Set DB_ALLOW_MISSING_SPLITS=1 to downgrade this to a logged warning and treat the missing subtree as empty (useful for recovering from a partial restore, but use with care).
"$split" is a reserved value. Because a bare marker on disk can only ever mean a real split, the engine refuses to store the string "$split" as a value anywhere in your data. Without that rule, a parent file saying {"k":"$split"} whose directory was missing would load as the literal string, replace the whole subtree, and the next flush would commit the loss — silently, with exit code 0. Refusing it at the door means a missing shard is unambiguous and can be reported instead.
Where files live
The file store is separate from the JSON tree and does not participate in any of the above. Files live under server/files/ (override with FILES_DIR), laid out as a plain directory tree that mirrors the path arrays you address them with — ["posts","p1","hero.png"] is server/files/posts/p1/hero.png.
Uploads use the same tmp + fsync + rename discipline: bytes land in a $tmp- prefixed file beside the destination and are renamed into place, so a reader sees the old file or the new one and never a partial write. Because a leading $ is a rejected path segment, an in-flight upload has no addressable path. Leftovers from a crash are swept at startup.
The daily backup covers server/data/ only. The file store is not copied — it is typically far larger than the JSON tree, and duplicating it daily on the same disk protects against very little. Back up server/files/ with whatever you already use for bulk storage.