Building a standard SaaS app is easy. You have a frontend, an API, and a database. If the internet cuts out, you show a dinosaur icon and the user waits.
But when you are building a Restaurant POS (Point of Sale), the internet will die on a Friday night at 8 PM. If the system stops, the restaurant loses money, chaos ensues, and we lose a customer.
We had to build a system that works perfectly with zero connectivity and magically syncs when the connection returns.
Here is the architecture behind our "Local-First" sync engine (and why AUTO_INCREMENT IDs nearly killed us).
The Architecture: Local-First, Cloud-Second
Most web apps follow this flow: Frontend -> API -> Database -> Response -> Update UI
This is too fragile for a POS. Instead, we use a Local-First architecture: Frontend -> Local DB (IndexedDB) -> Update UI -> Background Sync -> Cloud DB
Challenge #1: The ID Problem (Why ID: 1 is dangerous)
In a standard SQL setup, the database assigns IDs (1, 2, 3...). The Scenario:
Waiter A (Offline) creates an order. Local DB assigns ID: 50.
Waiter B (Online) creates an order. Cloud DB assigns ID: 50.
Waiter A comes back online. BOOM. Collision.
The Fix: UUIDs (Universally Unique Identifiers). We moved ID generation to the client-side. Every order gets a uuidv4() generated instantly on the device.
// Bad (Server reliance)
const order = await api.createOrder(data);
// Good (Offline ready)
const order = {
id: uuidv4(), // Generated instantly
timestamp: Date.now(),
items: [...]
};
await localDB.orders.put(order);
syncToCloud(order);
Challenge #2: The "Optimistic UI" Lie
We cannot wait for the server to confirm an action. The UI must be instant. We treat the Local DB as the "Source of Truth" for the UI. The React/Vue components subscribe only to the Local DB.
User clicks "Pay": We update Local DB -> UI updates instantly (Green checkmark).
Background Worker: Detects change -> Tries to POST to API.
If API fails: We retry exponentially (2s, 5s, 10s). The user never knows.
Challenge #3: Conflict Resolution (The "Last Write" Trap)
What happens if two waiters edit the same Table #4 at the same time while offline?
Waiter A adds "Coke".
Waiter B adds "Pepsi".
If we just overwrite the data, one drink disappears. The Fix: Delta Updates (CRDT-lite). We don't send the entire order object. We send "operations."
Action: ADD_ITEM, Item: Coke, Time: 8:01
Action: ADD_ITEM, Item: Pepsi, Time: 8:02 The server replays these operations in timestamp order. Both drinks end up on the bill.
The Tech Stack
Frontend: React (for the view layer)
Local Storage: RxDB / PouchDB (wrappers around IndexedDB)
Backend: Node.js
Sync: WebSockets for real-time + REST for bulk sync
Conclusion
Building "boring" B2B software often requires more complex engineering than the latest AI wrapper. Handling state across flaky 4G connections in a busy kitchen is the ultimate stress test.
If you are building SaaS, consider: Do your users really need to be online to use your product?
Top comments (1)
Interesting