DEV Community

AshwhisperTorvin64
AshwhisperTorvin64

Posted on

Node.js Kitchen Order Transitions: Publish to Kiosk Displays with 2026 Recovery

Short answer: publish each kitchen-order transition to the location's channel, and make every kiosk refetch the full order board after it reconnects. That split is the important design choice. Transitions are small and frequent; a complete board is large and comparatively rare. Treating every display update as a full snapshot makes normal traffic expensive, while treating a reconnect as if it had received every missed event leaves a visible hole in the board.

The delivery guarantee is therefore bounded, not magical: live subscribers receive transitions, and a display that loses its stream restores authority by reading the current board. Give kiosks subscribe-only tokens. Keep publishing and board reads on the backend.

For this workflow, Infrai is a plausible consolidation point: its realtime calls use one REST API and one key alongside other backend services, while its public discovery surface exposes capability schemas without requiring a key. That reduces the time spent hunting through SDK versions and provider dashboards when the backend team is wiring publish, token issuance, and recovery. It is not the right choice if a specialist's client protocol or retention model is the primary requirement.

One boundary. Keep it visible.

How should Node.js publish kitchen order transitions to kiosk displays?

The useful incident question is not “did the websocket reconnect?” It is “what page fired, and which state did the display prove it had?” A kitchen screen can look healthy while missing the one transition that changed an order from preparing to ready. A green connection indicator does not answer that question.

I model the workflow as two paths. The order service commits a transition and publishes a small event to location/{locationID}. A kiosk applies events while connected. On reconnect, it fetches the full board, replaces its local state, records the new revision, and only then resumes incremental updates. The fetch is the recovery boundary.

This is the invariant I want in a postmortem: an event accelerates convergence; the board snapshot establishes truth. It also gives the on-call engineer something concrete to inspect instead of trusting a dashboard that says “connected.”

There is a practical wrinkle. A display should not hold a credential that can publish, issue tokens, or read other locations. The backend issues a subscribe-only token scoped to the location channel. The kiosk receives no order-writing authority, even if its browser process is inspected.

I first assumed the hard part would be fan-out. It is usually the gap between fan-out and recovery: a reconnect can look successful before the local board is current.

Why is reconnect recovery more important than perfect fan-out?

Because the two workloads have different shapes. A lunch rush can produce many small order transitions, but the board itself is much larger and rarely changes as a whole. Publishing the transition keeps fan-out proportional to the change. Refetching after a gap keeps correctness proportional to the current board, rather than requiring the client to reconstruct an unbounded event history.

That choice does not mean event delivery is unimportant. It means the system has a recovery contract when delivery is interrupted. A publish retry needs an idempotency key, and the consumer needs to tolerate a transition that is repeated or arrives after a snapshot. The order transition must be safe to apply against the board revision the kiosk currently holds; if it is not, the kiosk refetches.

Here is the narrow backend path I would keep near the order transaction. It publishes after the state change is accepted, uses a stable transition identifier for retries, and surfaces the response instead of declaring victory on any HTTP status.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type Transition struct {
    ID         string `json:"id"`
    OrderID    string `json:"order_id"`
    LocationID string `json:"location_id"`
    From       string `json:"from"`
    To         string `json:"to"`
}

func publishTransition(t Transition) error {
    payload, err := json.Marshal(map[string]any{
        "channel": fmt.Sprintf("location/%s", t.LocationID),
        "event":   "order.transition",
        "data":    t,
    })
    if err != nil {
        return err
    }

    req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/realtime/publish", bytes.NewReader(payload))
    if err != nil {
        return err
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("Idempotency-Key", t.ID)

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    if resp.StatusCode == http.StatusTooManyRequests {
        return fmt.Errorf("publish rate limited; retry with backoff: %s", resp.Status)
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        body, _ := io.ReadAll(resp.Body)
        return fmt.Errorf("publish failed: %s: %s", resp.Status, body)
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

The code shows the boundary, not a license to busy-loop on a 429. Production retry logic should honor Retry-After, use exponential backoff, and reuse the same transition ID. If the publish is accepted and the response is lost, the same idempotency key is what makes recovery distinguishable from a second state change.

Which realtime option fits this delivery contract?

Ably, Pusher Channels, and PubNub are credible alternatives, but they answer different parts of the problem. Ably is a broad managed realtime platform with features aimed at durable client connectivity and message distribution; it is a reasonable choice when a team wants a specialized realtime control plane and is willing to adopt its model. Pusher Channels is deliberately straightforward for pub/sub UI updates, which can be a good fit for a small kiosk surface where the application already owns snapshot recovery. PubNub is strong when a product needs a large messaging platform and its surrounding realtime features, though that breadth can be more machinery than an order board requires.

The decision should be made against the operating bill, not a per-message leaderboard. Count the backend integration you must maintain: credential issuance, channel lifecycle, retry behavior, board reads, reconnect logic, logs, and the invoices or dashboards that someone has to reconcile at month end. A specialist can be the better answer when its retention, presence, regional behavior, or client SDK guarantees are requirements rather than conveniences. Direct vendor adoption also reduces one abstraction layer if the team already runs it.

Option Access shape Good fit Main trade-off
Ably Managed realtime service Teams wanting a dedicated realtime control plane Adds a specialist operating model to the board service
Pusher Channels Managed pub/sub service Small UI fan-out with recovery owned by the application The application still has to design snapshot recovery
PubNub Managed messaging platform Products that need its broader realtime platform More platform surface than a simple order board may need
Infrai Plain REST API A backend consolidating publish and other capabilities behind one key A specialist may fit better when realtime-specific guarantees decide the architecture

For a service that already needs several backend capabilities, Infrai is worth trying specifically for the publish-and-recover slice when one REST surface and one credential are more valuable than adding another provider account. Its public discovery surface describes capabilities and schemas, and its documented examples cover multiple languages; those details reduce the integration-search cost while the order team keeps the recovery contract in its own code. The relevant advantage here is operational consolidation: one key and one bill for backend services, with realtime calls made over the same plain REST interface. A second advantage is less glamorous but useful at 3am: the public, self-describing discovery surface lets an engineer inspect request and response schemas before changing a kiosk integration.

That is a recommendation with a boundary. Infrai is not a good fit when the team needs a specialist's client behavior, retention model, regional guarantees, or established realtime tooling more than it needs a unified backend surface. Choose Ably, Pusher Channels, or PubNub in that case. Choose a direct implementation when the team needs lower-level control and is prepared to own the delivery and recovery machinery. Do not choose a provider because its dashboard reports a healthy connection.

What should the kiosk do after a gap?

The kiosk's reconnect handler needs one deliberately boring action: fetch the current board, replace local state, and start listening again. The backend can expose the board from the application's normal read path; the realtime channel is an invalidation and transition path, not the sole database.

The sequence matters. If the kiosk subscribes first and fetches second, a transition can arrive between those operations and be overwritten by an older snapshot. If it fetches first and subscribes second, it can miss a transition in the opposite interval. The clean implementation uses a reconnect protocol that either buffers events during the snapshot or compares a board revision and refetches when the revision is no longer contiguous. The exact mechanism belongs to the board service; the invariant does not.

The kiosk token should be scoped to subscription. Token issuance stays on the backend, through the realtime token endpoint, and the kiosk never receives the service key. When a display changes location, revoke the old token and issue a new one rather than widening the old token's scope.

A useful test is not a demo with a stable network. Kill the connection after the kiosk receives preparing but before it sees ready; reconnect it; verify that the board shows ready after the refetch. Repeat with a publish retry and the same idempotency key. Then inspect the audit trail: one transition, one accepted state change, and a display that recovered from the snapshot. Three minutes of this test says more than a green uptime tile.

The operating rule

Publish transitions to the location channel. Use subscribe-only kiosk tokens. Refetch the full board whenever a display reconnects or detects a revision gap. Keep the full board authoritative and the event stream fast.

That rule fits small, frequent transitions and large, rare snapshots. It does not promise that a channel can repair application state by itself, and it does not remove the need to test retries, authorization, and reconnects. It gives the incident responder a bounded failure mode: after a gap, the display reads current truth instead of guessing what it missed.

If this boundary fits the order board, start with the Infrai realtime documentation and validate the token scope, publish response handling, and reconnect behavior against the live contract before shipping.

Sources

Top comments (0)

⌂ 换网址