Flat-File Storage

Using JSON files as a simple data store — and understanding when they break down

How Flat-File Storage Works

The simplest form of persistent storage: read data from a file, modify it in memory, write it back. This is exactly what our Module 05 REST tutorials do with items.json.

Request comes in:

1. Read entire file from disk     ┌──────────────┐
   ─────────────────────────────▶ │  items.json  │
                                  │ [{"id":1,    │
2. Parse JSON into memory         │   "name":"…"}│
   items = JSON.parse(data)       │  ...         │
                                  └──────────────┘
3. Modify array in memory
   items.push(newItem)

4. Write entire file back         ┌──────────────┐
   ─────────────────────────────▶ │  items.json  │
                                  │ [{"id":1,    │
                                  │   "name":"…"},│
                                  │  {"id":2,    │
                                  │   "name":"…"}]│
                                  └──────────────┘

Node.js Implementation

In Node.js, the fs module provides synchronous methods for reading and writing files. Here's the pattern used in our REST API tutorial:

const fs = require('fs'); const DATA_FILE = './items.json'; // Read all items function getItems() { const data = fs.readFileSync(DATA_FILE, 'utf8'); return JSON.parse(data); } // Save all items function saveItems(items) { fs.writeFileSync(DATA_FILE, JSON.stringify(items, null, 2)); } // Add a new item function addItem(newItem) { const items = getItems(); newItem.id = items.length > 0 ? Math.max(...items.map(i => i.id)) + 1 : 1; items.push(newItem); saveItems(items); return newItem; }

The pattern is straightforward: read the entire file, parse it from JSON into a JavaScript array, modify the array, then serialize it back to JSON and write the entire file. Every operation — create, update, delete — follows this same read-modify-write cycle.

PHP Implementation

PHP uses file_get_contents() and file_put_contents() for the same pattern. Because PHP starts fresh on every request, there's no option for in-memory persistence — file storage is the simplest persistent option available:

The Race Condition Problem

The biggest weakness of flat-file storage is what happens when two requests arrive at the same time. Consider this scenario:

Time    Request A                    Request B
────    ─────────                    ─────────
  1     Read items.json
        [item1, item2]
  2                                  Read items.json
                                     [item1, item2]
  3     Add item3 to array
        [item1, item2, item3]
  4                                  Add item4 to array
                                     [item1, item2, item4]
  5     Write items.json
        [item1, item2, item3]
  6                                  Write items.json
                                     [item1, item2, item4]

Result: item3 is LOST! Request B overwrote Request A's changes.

This is called a race condition — the result depends on the timing of the requests. With flat files, there's no built-in mechanism to prevent two processes from reading and writing the same file simultaneously.

File locking (flock() in PHP, advisory locks in Node.js) can mitigate this, but it adds complexity and doesn't scale well. Databases solve this problem properly with transactions and row-level locking.

Pros and Cons

Pros Cons
Zero setup — no database server to install No query language — must load entire file to find anything
Human-readable — open the file and see your data Race conditions — two requests writing simultaneously corrupt data
Easy to debug — just look at the JSON No indexing — searching gets slow with large datasets
Portable — copy one file to move all your data No relationships — no JOINs, no foreign keys
Great for learning the CRUD pattern Rewrites entire file on every change — inefficient