Skip to content

Case studyLive

Masa

QR menu, table ordering, a kitchen display, a waiter tablet and an owner admin for independent cafés and restaurants in Türkiye.

Role
Solo — product, design implementation, backend, infra
When
2026
  • Backend
  • Full-stack

The demo launcher opens the guest, kitchen, waiter and owner screens in one click each. Owner login: [email protected] / bostan-demo; test card 4242 4242 4242 4242. The demo venue resets every night; the source is private.

Masa’s two-phone demo: two guests at table 7 see the same live table cart.
Two phones, one table cart — everyone sees who added what, live.

Who it’s for

  • Owner-run neighbourhood places with 6–40 tables — a breakfast café in Kuzguncuk, a lokanta, a meyhane — that don’t want a POS contract or an app their guests have to download.
  • They need a menu that’s always current (sold-out items, allergens, four languages for tourists), orders that reach the kitchen without a notepad, a kitchen screen that doesn’t drop tickets when the Wi-Fi does — and a number at the end of the day.
  • Guests who scan the QR code on the table and order together: nothing to install, no sign-up.

What I built

  • Built it alone, end to end: a Hono API on Cloudflare Workers, D1 for the catalogue and history, and one SQLite-backed Durable Object per venue that owns the live floor — carts, orders, calls and bills.
  • Four front ends in one React app, ported 1:1 from an approved design prototype: the guest flow (TR/EN/DE/RU, allergen filter, shared cart, live order status, bill split), the kitchen display, the waiter tablet and the owner admin — plus a design-system page.
  • Exactly-once delivery: every change carries a client-generated action id, waits in an IndexedDB outbox until it is acknowledged, and is deduplicated by the Durable Object in the same transaction that applies it. A service worker keeps the kitchen screen running offline.
  • The owner’s side: a menu builder with modifiers and translations, printable QR table tents and an end-of-day report with CSV — plus PBKDF2 auth with httpOnly sessions, rate limits, a fake PSP behind a PaymentProvider interface, a nightly cron and a chaos test that proves the invariant.

How it works

  1. 1Scan

    The QR tent on table 7 opens the menu. The guest picks a language (TR/EN/DE/RU), types a name and can hide allergens.

  2. 2Build the cart together

    Each phone adds items with modifiers and a note for the kitchen; every phone at the table sees the same cart live, including who added what.

  3. 3Send

    One tap sends the whole table cart. If two people press send at once, exactly one order goes out; if the cart changed while you were reading, nothing is sent until you’ve seen it.

  4. 4Kitchen → waiter → table

    The ticket lands on the kitchen screen within milliseconds; its timer turns amber at 8 minutes and red at 15. The cook bumps it, the guest’s tracker moves on and the waiter sees it’s ready to serve.

  5. 5Pay

    Split equally, by item or by a custom amount, add a tip and pay with the test card — or ask for the bill and pay at the counter. The table is then closed and archived.

Key decisions

  1. 01

    One Durable Object per venue owns the live floor

    Instead of: A shared database with locks or transactions across requests.

    Why: Carts, orders, calls and bills need a single writer. A Durable Object serialises every action for free — no locks, no distributed transactions — and its output gate means an ack is never sent before the write is durable. D1 holds the catalogue and the history; the hot path never reads it.

  2. 02

    Exactly-once by action ids, not by careful clients

    Instead of: Retry and hope the server notices duplicates.

    Why: A phone on café Wi-Fi only gets at-least-once delivery. A client-side outbox plus a server-side dedupe log turns that into exactly-once effects. Changes commute where they can — quantities are deltas, kitchen ticks set rather than toggle — and an order names the cart revision the guest confirmed.

  3. 03

    Hibernating WebSockets for the fan-out

    Instead of: Polling, or sockets that keep the object awake.

    Why: Phones, kitchen screens and waiter tablets hold sockets tagged by role and table. Idle sockets cost no Durable Object time and pings are answered by the runtime without waking it. Only /api/* invokes the Worker; the app itself is served as free static assets.

  4. 04

    Port the design, don’t approximate it

    Instead of: Rebuild the screens with a UI kit that looks close enough.

    Why: The approved prototype is the spec: its CSS is ported verbatim and its screens rebuilt 1:1 as React components — the tokens, the kitchen ticket, the table states and the TR/EN/DE/RU copy. Screens it only implied use the same tokens and components.

Architecture

DEVICESGuest phonesReact · shared cartKitchen displayservice worker ·IndexedDB outboxWaiter & ownerfloor plan · adminWSS actions · HTTPS adminEDGECloudflare Workerstatic assets · /api/* firstAPPLICATIONHono APIauth · menu · paymentsVenue DO1 per venue · action-iddedupemenu push · archiveDATAD1menu · users · orderhistory · reportsDO SQLitethe live floor — onewriterOUTSIDEPaymentProviderfake PSP · test cardsCron 04:00archive · report · demoreset↑ ships everything aboveDELIVERYUnit tests11 · domainWorkers tests15 · real runtimeChaos run40 tables × 3 phoneswrangler deployone deploy

One Worker deploy serves the app, the API and the WebSockets; the venue’s Durable Object is the only writer of the live floor. Everything here is mine.

Numbers

lost / duplicate orders in the chaos run
0 / 0
connection drops in that run — 4,336 actions replayed
535
order → kitchen screen, p50 (p95 87 ms, local)
30 ms
a month — Cloudflare’s free plan
€0

Tech stack

Backend

  • Cloudflare Workers
  • Durable Objects
  • D1 (SQLite)
  • Hono
  • TypeScript
  • WebSocket hibernation
  • Cron Triggers
  • Rate Limiting
  • PBKDF2

Frontend

  • React
  • React Router
  • Vite
  • Service worker
  • IndexedDB
  • qrcode-generator

Quality

  • Vitest (Workers pool)
  • Chaos script
  • wrangler dry-run

Want the longer version — the code, the pipeline, the trade-offs?

Email me
Next case study3dfilamentciAn e-commerce platform for 3D printers, filament, resin and spare parts — built from scratch for a specialist shop.