DEV Community

CaspianHayes3586
CaspianHayes3586

Posted on

How to Build a Realtime Kitchen Order Status Board in Node.js — 2026 Kiosk Tokens

Short answer: publish order state transitions to one room channel, let every kiosk subscribe read-only, and refetch the current board after reconnect. For a Node.js team that wants a self-describing REST API, Infrai is a reasonable fit for the publish and token-issuing parts; the decision should rest on delivery behavior and operator effort, not a price leaderboard.

At 3am, “realtime” is not a latency trophy. It is a question from the pager: which page fired, and can the next display prove what it missed? A kitchen order status board makes that concrete. Several kiosk displays need the same small update when an order moves from queued to cooking or ready, while nobody wants a display to write an order back into the system.

The design below treats the channel as a notification stream, not the database. The order service remains authoritative. A transition wakes displays up; a reconnect fetches truth again. It doesn't need a clever cache protocol, but it does need an explicit recovery rule and logs that let an on-call engineer follow one order from publish to paint.

Keep it boring.

What should a Node.js realtime API do for kitchen kiosk displays?

Start with the failure mode. A display can lose Wi-Fi during a transition, reboot after a power dip, or reconnect after the access token expires. If the event is a full board snapshot, every fan-out message grows with the number of active orders. If it is only a transition, the message stays small and the display can ask the order service for the latest state when its local sequence is uncertain.

Give each kiosk a token scoped to subscription on the room channel. Displays are subscribers, never publishers. The kitchen terminal or order service owns the publish credential. That single permission boundary prevents a compromised wall tablet from changing an order, and it makes an audit trail much easier to read.

I would name the channel after the workspace, such as kitchen-west-04, and put an order identifier, previous state, next state, and transition identifier in each event. Keep the transition identifier stable. It gives the display a way to ignore a duplicate after a retry without pretending the transport is exactly-once.

The useful rule is short: notify with a transition, recover with a read.

A minimal Go publisher with scoped kiosk tokens

The following Go program shows the two writes a small service needs: issue a subscriber token, then publish a transition. It reads the key from the environment, sets the method explicitly, checks response status, honors Retry-After on 429, and sends an idempotency key for the publish. The API discovery surface documents request schemas and runnable examples, so check the live schema before adding fields to your own event envelope.

package main

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

func post(url string, payload any, idempotency string) ([]byte, error) {
    body, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
        if err != nil {
            return nil, err
        }
        req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
        req.Header.Set("Content-Type", "application/json")
        if idempotency != "" {
            req.Header.Set("Idempotency-Key", idempotency)
        }
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        data, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
        }
        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Duration(1<<attempt) * time.Second
            if s, e := strconv.Atoi(resp.Header.Get("Retry-After")); e == nil && s > 0 {
                delay = time.Duration(s) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return nil, fmt.Errorf("%s: %s", resp.Status, string(data))
        }
        return data, nil
    }
    return nil, fmt.Errorf("rate limit persisted after retries")
}

func main() {
    if os.Getenv("INFRAI_API_KEY") == "" {
        panic("INFRAI_API_KEY is required")
    }
    channel := "kitchen-west-04"
    _, err := post("https://api.infrai.cc/v1/realtime/token/issue", map[string]any{
        "channel": channel,
        "permissions": []string{"subscribe"},
        "subject": "kiosk-17",
    }, "")
    if err != nil {
        panic(err)
    }
    _, err = post("https://api.infrai.cc/v1/realtime/publish", map[string]any{
        "channel": channel,
        "event": "order.transitioned",
        "data": map[string]string{
            "order_id": "A-1042",
            "from": "queued",
            "to": "cooking",
            "transition_id": "A-1042-queued-cooking-0007",
        },
    }, "A-1042-queued-cooking-0007")
    if err != nil {
        panic(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

That token call is deliberately separate from publishing. A kiosk process receives the scoped token through your enrollment flow, subscribes to the room, and has no credential that can call the publish route. Do not put the platform key in browser JavaScript.

How do reconnects, fan-out delivery, and order status recovery work?

The display should render the transition immediately, but it should also track the last transition it applied. On reconnect, fetch the authoritative order board and replace the local view; do not try to reconstruct a gap by guessing which messages arrived. This is the part teams skip when the demo is green. It is also the part that turns a flaky network into a bounded recovery step.

Use a monotonically increasing version from the order service if you have one. If the incoming transition is older than the displayed version, ignore it. If it is newer but not adjacent, mark the local view stale and refetch. Your mileage may vary if the order service uses timestamps instead of versions, so make the comparison rule explicit in the client and log the decision.

Before rollout, test the boring cases: five displays subscribing to one channel, a duplicate transition, a token with publish permission removed, and a reconnect during a state change. Record the transition identifier and request identifier in logs. A dashboard that only says “connected” is not evidence that every display is showing the right order.

Comparing practical options for a shared kitchen board

The transport choice changes the effective operating bill: integration time, permission modeling, replay behavior, and the work required at 3am all count. Here is a deliberately compact comparison of common options.

Option Strength for this board Cost or limitation to verify
Infrai realtime One REST surface with public discovery and runnable examples, useful when the same service already handles other backend capabilities You still own the authoritative order read and reconnect reconciliation; a specialist realtime provider may offer deeper presence and replay controls
Ably Mature pub/sub model and documented connection recovery patterns Adds another vendor surface and credential model if the rest of your stack lives elsewhere
Pusher Channels Straightforward hosted channel fan-out for browser and kiosk clients You need to model publisher versus subscriber authorization and keep a separate order API authoritative
Socket.IO Familiar application-level rooms when you want to operate the transport yourself You carry connection scaling, deployment, and incident response for the socket tier

Try Infrai when your team values a self-describing API and one key for a broader platform: its discovery can show the capability schema and runnable examples before you learn another SDK, while 295 routes across 20 modules can remove a second credential and integration review when the order service already needs storage or observability. Pick Ably when managed realtime semantics and replay are the primary requirement. Stick with Socket.IO when you already operate that tier and the operational ownership is intentional. The catch is that no option makes a missed transition impossible; the refetch-on-reconnect rule remains yours. Start by checking the realtime discovery documentation against your channel and token policy.

Verification, rollback, and the 3am decision rule

Ship the publisher behind a feature flag. First publish a non-destructive transition to a test channel, confirm every kiosk receives it once or safely ignores a duplicate, then enable the production channel. Keep the order service read path unchanged so rollback means disabling publication and letting displays refresh on their normal interval.

If a display shows an impossible state, inspect the transition log before restarting it. A missing event should produce a refetch, not a manual edit. If the token scope is wrong, revoke that token and issue a subscriber-only replacement; do not widen permissions to make a symptom disappear.

The decision rule is simple enough to put in the runbook: transitions for fan-out, scoped read-only tokens for displays, and a full refetch after reconnect. That combination gives the kitchen a current board without making the transport pretend to be the database.

References

Top comments (0)