A restaurant management system is not a simple CRUD application. It coordinates real-time order flow between front-of-house tablets, kitchen display screens, and inventory systems while handling the operational reality that internet connectivity in Lebanon and many MENA markets is unreliable. The architecture has to work when the network does not.
A restaurant management system is not a simple CRUD application. It coordinates real-time order flow between front-of-house tablets, kitchen display screens, and inventory systems while handling the operational reality that internet connectivity in Lebanon and many MENA markets is unreliable. The architecture has to work when the network does not.
This is the architecture behind RTYLR, Voxire's commerce platform for restaurants and retail businesses. RTYLR runs at locations ranging from single-outlet cafes in Beirut to multi-branch chains across the Gulf. The architecture has been shaped by both the technical complexity and the practical constraints of running software in markets where power cuts and connectivity interruptions are normal operating conditions.
The core components
A complete restaurant management system involves more interconnected components than most engineering teams estimate at the start:
POS terminals: The primary customer-facing interface. Takes orders, processes payments, prints receipts. Must work offline.
Kitchen Display System (KDS): Shows orders to kitchen staff in the preparation queue. Order appears on the KDS the moment it is placed, with urgency indicators and preparation time tracking.
Inventory system: Tracks stock levels in real time. Every item sold decrements the relevant ingredients. Low-stock alerts fire before items run out, not after.
Table management: For dine-in operations, tracks table status, party size, and order duration. Links multiple orders to a single table session.
Reporting backend: Aggregates data for end-of-day reconciliation, period-over-period comparisons, and staff performance tracking.
Owner dashboard: A web interface for operators to view live sales, manage menus, and configure the system without going to the terminal.
Where the architecture decisions get hard
Three properties create the most design tension: offline operation, real-time coordination, and multi-location consistency.
Offline operation means the POS terminal must accept orders, process cash payments, and print receipts with no network connection. This requires a local SQLite database on each terminal with the full menu and configuration. Orders created offline queue locally and sync when connectivity returns.
Real-time coordination means that when a KDS shows an order, the kitchen sees it within seconds of it being placed. This requires WebSocket connections from the KDS to the backend, with the backend pushing order events as they arrive. When connectivity to the backend is lost, the KDS falls back to a local cache of the current queue.
Multi-location consistency means that a chain's owner can see accurate stock levels and sales figures across all branches from a single dashboard. The backend aggregates events from all terminals and provides consistent reads. During a network partition, each branch operates independently and reconciles when reconnected.
The sync architecture
The most important design decision in the entire system is how terminal state synchronizes with the backend.
Each terminal has a monotonically increasing local sequence number. Every write to the local SQLite database increments this counter. When the terminal reconnects, it sends the backend its last known server sequence number. The backend returns all events since that sequence. The terminal applies them in order.
type SyncRequest struct {
TerminalID uuid.UUID `json:"terminal_id"`
LocalSeq int64 `json:"local_seq"`
LastServerSeq int64 `json:"last_server_seq"`
PendingEvents []Event `json:"pending_events"`
}
type SyncResponse struct {
ServerSeq int64 `json:"server_seq"`
NewEvents []Event `json:"new_events"`
ConflictedIDs []uuid.UUID `json:"conflicted_ids,omitempty"`
}
Conflicts arise when the same resource was modified both offline on the terminal and on the server. Menu item price changes, for example. The conflict resolution policy is documented per resource type: menu changes from the server always win over local cache, but orders created offline are always preserved because they represent actual transactions.
Inventory tracking without over-engineering
The common mistake with restaurant inventory is trying to track at too granular a level from the start. A database row for every lettuce leaf is not useful. A database row for every batch of lettuce received, with decrements as dishes containing lettuce are sold, is.
The right granularity is the purchasing unit: cases, kilograms, liters. Each menu item has a recipe that maps to deductions from these units.
CREATE TABLE inventory_items (
id UUID PRIMARY KEY,
location_id UUID NOT NULL,
name TEXT NOT NULL,
unit TEXT NOT NULL,
quantity NUMERIC(10,3) NOT NULL,
low_stock_threshold NUMERIC(10,3)
);
CREATE TABLE menu_item_recipes (
menu_item_id UUID NOT NULL REFERENCES menu_items(id),
inventory_item_id UUID NOT NULL REFERENCES inventory_items(id),
quantity_used NUMERIC(10,4) NOT NULL,
PRIMARY KEY (menu_item_id, inventory_item_id)
);
When an order is confirmed, a database transaction decrements inventory based on the recipe. The decrement happens on the server when the order syncs, not on the terminal. This prevents double-deductions when an offline terminal syncs orders that the server has not yet seen.
The kitchen display system
The KDS has a simple requirement that is technically nontrivial: it must show the current preparation queue in real time and update within seconds when orders arrive, change state, or are completed.
WebSockets are the right transport here. Each KDS connects to the backend on startup and subscribes to its location's order stream. The backend maintains a list of active KDS connections per location and fans out order events using Go channels.
type KDSHub struct {
mu sync.RWMutex
clients map[uuid.UUID]map[*KDSConn]bool // locationID -> connections
}
func (h *KDSHub) BroadcastToLocation(locationID uuid.UUID, event OrderEvent) {
h.mu.RLock()
conns := h.clients[locationID]
h.mu.RUnlock()
for conn := range conns {
select {
case conn.send <- event:
default:
// Slow client, close and remove
h.remove(locationID, conn)
}
}
}
The KDS stores a local copy of the current queue. If the WebSocket disconnects, the KDS continues showing the last known state and displays a connectivity warning to kitchen staff. When reconnected, it requests the full current queue and resyncs.
Reporting that is actually used
End-of-day reports and period comparisons are the most-used features by restaurant operators in Lebanon and the Gulf. The most useful reports in RTYLR practice are:
Hourly sales breakdown: Shows peak and off-peak hours. Used for staffing decisions.
Item performance: Revenue and quantity per menu item. Used for menu optimization.
Table turn rate: Average time per table for dine-in operations. Directly correlates with revenue per seat.
Void and refund analysis: Unusual void rates indicate staff training issues or system problems.
All of these are aggregations on the orders table with appropriate indexes. For real-time dashboards, materialize the aggregates every five minutes rather than running expensive aggregation queries on every request.
Key lessons from RTYLR operations
Four lessons shaped the architecture after a year of production operation:
First, the sync conflict rate is lower than expected but the consequences of missed conflicts are higher than expected. A menu price applied at the wrong time due to a sync conflict results in incorrect billing. Build conflict logging and operator review into the system from day one.
Second, kitchen display screen hardware in Lebanon runs hot. Terminals in environments without air conditioning need software that degrades gracefully when memory and CPU limits are hit. The KDS uses a page-based order queue rather than an infinite scroll to cap memory usage.
Third, every restaurant wants custom reporting. Building a flexible report query layer that operators can configure is worth the investment. The alternative is a stream of custom reporting requests to the engineering team.
Fourth, offline mode is not a fallback, it is the primary operating mode for many restaurants. Design for offline first, add connectivity as an enhancement.
Enjoying this article?
Enter your email and get a clean, formatted PDF of this article - free, no spam.
Not sure where to start?
If you are building a restaurant or retail management system for the Lebanese or MENA market, Voxire has built production systems of this complexity and can help you avoid the architecture mistakes that cost months to fix.
https://voxire.com/get-a-quote/


