# Real-time Order Notifications: The Royal Madrasi Pattern
By Govind Bajaj · 2025

Tags: pusher, real-time, restaurant, architecture, notifications
200 orders per hour. 12 kitchen staff. 4 admin screens. Every order must appear everywhere instantly. Here's the pattern that keeps Royal Madrasi running during rush.
The Royal Madrasi restaurant processes two hundred orders per hour during lunch rush.

Twelve kitchen staff need to see new tickets instantly. Four admin screens must show live sales and order status. And when the kitchen marks an order ready, every connected device must reflect it within half a second.

This is the real-time architecture pattern I built for Royal Madrasi. It has been running for eight months without a single missed notification during service hours.

## The Problem: Restaurant Timing Is Unforgiving

A customer places an order. The kitchen needs to see it in under two seconds or the ticket misses the current batch. The manager needs to see sales update in real-time to predict inventory. And when food is ready, the front desk needs to know immediately so they can call the customer.

Traditional polling — hitting the server every five seconds — creates three problems. It delays notifications by an average of 2.5 seconds. It hammers the server with unnecessary requests during slow periods. And it scales poorly — fifty connected devices polling every five seconds is six hundred requests per minute even when nothing is happening.

## The Pattern: Event-Driven with Pusher Channels

The architecture has three layers. The client subscribes to channels and listens for events. Pusher maintains the connection and routes events to subscribers. The server triggers events when data changes.

Order created: Save to DB, then trigger a new-order event, and all admins receive it. Status updated: Update DB, then trigger a status-changed event, and the kitchen sees it.

The database is always the source of truth. The real-time layer is only for display synchronization. If Pusher goes down, orders still save. When it comes back, clients reconnect and get back in sync.

## The Channel Strategy

I use three channel types for different broadcast patterns.

Global channel (orders): Everyone subscribes. Used for new orders and status changes. Simple, reliable, broadcasts to all connected clients.

Filtered on client: The server sends all order events. The client decides which to display. Kitchen staff only see orders assigned to their station. Admins see everything.

Private channels (admin-dashboard): Require authentication. Only managers and admins can subscribe. Used for sensitive data like revenue numbers and refunds.

## Handling the Rush: Backpressure and Batching

During peak hours, twenty orders might arrive in a single minute. Sending twenty separate Pusher events creates twenty separate re-renders on every client.

I batch events that occur within a 500ms window:

```ts
// lib/batch-publisher.ts
class BatchPublisher {
  private buffer: any[] = []
  private timer: NodeJS.Timeout | null = null
  private readonly BATCH_WINDOW = 500 // ms

  add(event: any) {
    this.buffer.push(event)
    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.BATCH_WINDOW)
    }
  }

  private flush() {
    if (this.buffer.length === 0) return
    if (this.buffer.length === 1) {
      pusher.trigger("orders", "new-order", this.buffer[0])
    } else {
      pusher.trigger("orders", "new-orders-batch", {
        orders: this.buffer,
        count: this.buffer.length,
      })
    }
    this.buffer = []
    this.timer = null
  }
}

export const orderPublisher = new BatchPublisher()
```

The client receives either a single order or a batch. Both are handled by the same state update logic.

## Connection Resilience

Restaurant WiFi is unreliable. Kitchen equipment interferes with signals. Staff close and reopen their devices throughout the day.

```tsx
// Connection state tracking
pusher.connection.bind("connected", () => {
  setConnectionStatus("connected")
  syncMissedOrders()
})

pusher.connection.bind("disconnected", () => {
  setConnectionStatus("disconnected")
  showWarning("Connection lost. Reconnecting...")
})

// Sync missed orders on reconnect
async function syncMissedOrders() {
  const lastSync = localStorage.getItem("lastSyncTime")
  const missed = await fetch(`/api/orders/sync?since=${lastSync}`)
  missed.forEach(order => addTicket(order))
  localStorage.setItem("lastSyncTime", new Date().toISOString())
}
```

The sync endpoint returns all orders created since the given timestamp. This ensures no order is ever lost, even during extended disconnections.

## Takeaways

- Database first, broadcast second — the real-time layer is for display sync, not data persistence
- Batch rapid events to reduce client re-renders during peak load
- Filter on the client for role-specific views — one channel, multiple presentations
- Implement sync-on-reconnect for unreliable network environments
- Use private channels for sensitive data that not all staff should see
- Play audio notifications for time-critical events (new orders)
- Log connection state changes to diagnose network issues
- This pattern works for any real-time dashboard: orders, IoT sensors, live analytics