Strengthen JSON Persistence
The project deliberately uses a JSON file instead of a database server. That keeps the learner setup small, but a simple file still needs a safe write strategy.
Open server/src/db.js. The file keeps the current database in memory and writes changes back to disk. The important operations are getDb() for reading and updateDb() for mutations.
The mutation path uses a queue:
let operationQueue = Promise.resolve();
export function updateDb(mutator) {
const operation = operationQueue.then(async () => {
const db = await load();
// apply one mutation and persist it
});
operationQueue = operation.catch(() => {});
return operation;
}
Here is the idea behind the queue. operationQueue starts as an already-resolved promise. Each new mutation waits for the previous mutation to finish before it starts. That means two writes cannot both read an old snapshot and then overwrite each other’s work.
Inside the queued operation, keep a snapshot before running the mutator:
const previous = structuredClone(db);
If the mutation or the save fails, restore that snapshot:
catch (error) {
cache = previous;
throw error;
}
This matters because the application keeps the database in memory. If the disk write fails but the memory object keeps the new values, the running process and the JSON file would disagree.
The actual write uses a temporary file first:
await writeFile(TEMP_PATH, JSON.stringify(cache, null, 2), "utf-8");
await rename(TEMP_PATH, DB_PATH);
Writing to a temporary file first means the existing db.json is not replaced until the complete JSON content has been written. The rename then swaps the finished file into place.
This is still not a production database, and we are not pretending it is. The goal is to make the deliberately small learner persistence layer behave predictably.
Test it
Add two entries quickly from the UI and then inspect server/data/db.json. Both changes should be present and the file should contain valid JSON.
You can also temporarily make writeFile() fail during development and confirm that the API reports the error and the in-memory data returns to its previous snapshot.
Checkpoint
You can now explain why updateDb() queues mutations, keeps a rollback snapshot, and writes through a temporary file instead of replacing db.json immediately.