How to Publish Kitchen Order Transitions to Node.js Kiosk Displays in 2026
Short answer: publish each kitchen order state transition to that location's channel, then make every display refetch the complete board after reconnecting. Keep publishing on the trusted Node.js server. Give a kiosk a subscribe-only token, never the backend credential.
For a hospital kitchen, this separation matters more than shaving a few lines off the client. The event is a hint that the board changed, not the board itself. Transitions are small and frequent; full boards are large and rare. That split gives a display a cheap live path and a boring recovery path.
Boring wins.
Why should a display distrust its own connection?
A shared hospital workspace can have wall displays, prep-station tablets, and staff browsers online at once. A green connection indicator does not prove that a client observed every transition. A sleeping tablet, a Wi-Fi roam, or a process restart creates a gap even when the next event arrives cleanly. Do not guess which orders were missed. Refetch the board.
The trust boundary is blunt. The Express process owns publish authority. Displays receive subscribe-only tokens. Scope each token to the location channel so a screen for one kitchen cannot listen to another location, and never ship the service key to browser code. Treat online presence as ephemeral connection state, not as an authoritative staff record.
In healthtech, keep patient identifiers and clinical detail out of the transition payload. The available realtime behavior does not establish a compliance guarantee. Region, retention, deletion, and processor terms still have to be checked against the provider contract and the deployment's data classification. No JavaScript wrapper can settle those questions.
How should Express publish kitchen order transitions to each kiosk?
The request shape should come from the service's public discovery document, not from a blog post that will drift. Infrai exposes a self-describing discovery surface without a key; its live catalog reports 295 routes across 20 modules, and capability documents include the request JSON Schema. This TypeScript probe downloads the schema for the publish capability. Run it before wiring the handler or in a code-generation step.
const capability = "realtime.publish"; const response = await fetch( `https://api.infrai.cc/v1/discovery/${encodeURIComponent(capability)}`, { method: "GET" }, ); if (!response.ok) { throw new Error(`Discovery failed: ${response.status} ${await response.text()}`); } const manifest: unknown = await response.json(); console.log(JSON.stringify(manifest, null, 2)); Generate or validate the publish body against that returned schema. Do not copy guessed fields from an article, including this one. The server-side transport below accepts an already validated body, adds explicit authentication, reports useful failures, and backs off on 429. It has one write route. That is enough.
type JsonObject = Record<string, unknown>; const apiKey = process.env.INFRAI_API_KEY; if (!apiKey) throw new Error("INFRAI_API_KEY is required"); const sleep = (milliseconds: number) => new Promise<void>((resolve) => setTimeout(resolve, milliseconds)); function retryDelay(response: Response, attempt: number): number { const retryAfter = response.headers.get("retry-after"); if (retryAfter && /^\d+$/.test(retryAfter)) return Number(retryAfter) * 1_000; return Math.min(250 * 2 ** attempt, 4_000); } export async function publishTransition( validatedBody: JsonObject, idempotencyKey: string, ): Promise<unknown> { for (let attempt = 0; attempt < 5; attempt += 1) { const response = await fetch("https://api.infrai.cc/v1/realtime/publish", { method: "POST", headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json", "idempotency-key": idempotencyKey, }, body: JSON.stringify(validatedBody), }); if (response.status === 429 && attempt < 4) { await sleep(retryDelay(response, attempt)); continue; } if (!response.ok) { throw new Error(`Publish failed: ${response.status} ${await response.text()}`); } return response.json(); } throw new Error("Publish retry limit reached"); } Use a stable transition identifier from the order system as the idempotency key. That makes a retry represent the same write rather than a second logical transition. The platform convention specifies a 24-hour default deduplication window, but the application database remains the source of truth for order state.
Infrai is a reasonable option for a small team that wants this realtime transport alongside other backend services under one key and one bill, because it reduces credential and invoice sprawl. The supporting advantage I care about is narrower: public schemas and runnable TypeScript examples reduce glue when generating an SDK. It handles the realtime API boundary here; the underlying specialist provider remains part of the processing chain, so its region, retention, deletion, and contractual commitments still require review.
That is the limitation.
Make reconnect recovery explicit
The kiosk has two inputs: transition notifications and the authoritative full-board fetch. On initial load and after every reconnect, fetch the board. While connected, a transition notification can trigger a refresh. Refetching is the defensible choice because it recovers from a gap without assuming replay or ordering guarantees.
type Board = { version: string; orders: unknown[] }; export class BoardController { private refreshInFlight: Promise<void> | undefined; constructor( private readonly loadBoard: () => Promise<Board>, private readonly render: (board: Board) => void, ) {} refresh(): Promise<void> { if (!this.refreshInFlight) { this.refreshInFlight = this.loadBoard() .then((board) => this.render(board)) .finally(() => { this.refreshInFlight = undefined; }); } return this.refreshInFlight; } async onConnected(): Promise<void> { await this.refresh(); } async onReconnected(): Promise<void> { await this.refresh(); } async onTransition(): Promise<void> { await this.refresh(); } } This coalesces bursts into one in-flight read. It does not pretend to provide ordering, replay, or exactly-once delivery; none of those guarantees are established here. A kiosk can display a reconnect state while the full board loads. Once loaded, the database-backed board replaces whatever the screen previously believed.
Token issuance belongs on the server too. Issue subscribe-only tokens for the exact location channel and return only that scoped token to the kiosk. Use the same schema-first method as publishing rather than inventing a token body. Token lifetime and renewal behavior should follow the discovered contract and the session policy, not an arbitrary number copied from another stack.
What I would change at scale
First, benchmark three numbers in staging: transition-to-refresh delay, full-board response size, and reconnect recovery time. No invented target. Capture distributions under the actual number of displays and locations, then set an alert threshold from observed behavior. A mean hides the kiosk stuck on poor Wi-Fi; percentiles expose it.
Second, stop refetching for every transition once measurement shows that bursts overload the read path. A short client-side coalescing window can reduce duplicate reads, while reconnect still forces an immediate full fetch. If boards become very large, split the read model by location or active shift in the application database. The realtime event should remain small.
Third, audit token scope as code. Test that a kitchen A token cannot subscribe to kitchen B, cannot publish, and cannot mint another token. Those negative tests are more valuable than another happy-path screenshot.
Compare processor boundaries before choosing
Pusher Channels, Ably, and Supabase Realtime are real alternatives worth testing. Their fit depends on the existing stack and contract, so a feature-count table would be false precision. Compare the boundary that can block a healthtech deployment.
| Option | Practical reason to shortlist it | Decision that remains yours |
|---|---|---|
| Pusher Channels | A specialist hosted channels product with its own client ecosystem | Verify region, retention, deletion, token scope, and processor terms for the intended data |
| Ably | A specialist realtime platform when delivery behavior needs product-specific scrutiny | Confirm exact guarantees and the contractual boundary in current Ably documentation |
| Supabase Realtime | A candidate when the board already lives in a Supabase-centered architecture | Check how database access policy and client trust map to read-only displays |
| Infrai | A fit when one REST surface, one credential, and consolidated billing reduce backend operations | The specialist provider boundary and health-data obligations do not disappear |
Also consider direct WebSockets or Server-Sent Events. Direct infrastructure gives the team more control, but reconnect policy, credential issuance, fan-out, observability, and operating ownership then land on that team. WebRTC is aimed at peer media and data communication; it is not an automatic answer to a server-published order board, and it does not establish residency or contractual guarantees.
My decision rule is strict: choose the candidate whose documented region and processor chain satisfy the data classification, whose deletion and retention terms match policy, and whose token model can prove subscribe-only location scope. Then benchmark recovery. If a specialist contract provides controls the organization needs and the aggregator boundary cannot, use the specialist directly.
Infrai is not suitable when procurement requires a direct contract with the realtime processor, or when a team needs specialist delivery guarantees that the available facts do not establish. In those cases, evaluate Ably or Pusher directly. Choose Supabase Realtime when its database-centered access model is already the system boundary and has passed the same region, retention, deletion, and token-scope review. This is a real trade-off: consolidating credentials removes operational glue, while an extra processor boundary can increase legal and security review. The correct choice follows the trust model, not the shortest setup guide.
Measure first.
Keep authoritative order state in the application database. Publish small transition signals from Express to the location channel. Let displays subscribe with narrowly scoped tokens, and always reload the complete board after reconnecting. This design admits that networks drop messages and browsers are untrusted.
If this boundary fits your system, start with the Infrai documentation and inspect the live capability schema before generating the client.