Building a Commission-Free QR Menu & Ordering Platform for Independent Restaurants
By Shiva Prasad
A multi-tenant SaaS that turns a restaurant's paper menu into a mobile menu, a per-table QR code and a direct ordering channel — built so an independent restaurant keeps its margin instead of paying a marketplace commission of up to 30%.
Platform
Web (Next.js + NestJS)
Duration
4-day build sprint
56
Domain model tables shipped in one schema
2×
Tenant isolation enforced independently (ORM + Postgres RLS)
30%
Marketplace commission the direct-ordering channel is designed to recover
Project overview
A commission-free direct ordering channel, live end-to-end from QR scan to kitchen queue, built and verified in the same week the specification was finalized.
Platform
Web (Next.js + NestJS)
Duration
4-day build sprint
Type
Multi-Tenant SaaS
Stack
13 technologies
The challenge
Independent restaurants wanting online ordering are pushed onto delivery marketplaces charging commissions of up to 30% in France, while cheap QR-menu tools treat allergen data as free text and model prix-fixe formule menus as unstructured text instead of orderable choice groups. Codeprism built Carte Numérique — a multi-tenant SaaS that gives a restaurant its own digital menu, per-table QR ordering and a kitchen order queue — specified and stress-tested against a real, corrupted paper-menu scan from its first design partner, Royal Punjab in Paris.
Marketplace commissions of up to 30% eat directly into delivery margin
Menus frozen in print — a price change means a reprint, not an edit
Cheap QR-menu tools treat allergen declaration as free text, not the structured legal requirement it is under EU 1169/2011
Prix-fixe formule menus modelled as text, not as orderable choice groups — where the lunch trade actually happens
Comparable products rely on one restaurant-wide QR plus a manually typed table number, so orders can't be reliably attributed to a table
What we set out to do
- 01
Let a restaurant onboard itself, build its own digital menu, and publish it without developer involvement
- 02
Give every table its own QR code that carries the diner straight into an order tagged to that table — no app install, no manual table entry
- 03
Let kitchen and front-of-house staff see incoming orders grouped by table and move each through its lifecycle in real time
- 04
Give the platform operator a cross-tenant console to see every onboarded restaurant and act on one without database access
- 05
Model the full long-range domain up front, so the MVP and every later phase land on the same schema with no re-migration
How we solved it
A Full Domain Model, Shipped Once
Modelled the entire restaurant-commerce domain — tenancy, catalogue, pricing, formules, allergen/diet declarations, ingestion, presence (zones/tables/QR), ordering, payments, fulfilment and billing — as a single 56-model, 22-enum Prisma schema, before writing a single controller.
Key decision
Design the full long-range domain up front, even though the MVP touches only a fraction of it
Result
56 models covering every aggregate in the long-range spec; no schema changes required to ship the MVP feature set
Tenant Isolation Enforced Twice
Tenant context is resolved once, from a verified JWT, in middleware via AsyncLocalStorage — never from a client-supplied header. From there it's enforced twice: a Prisma client extension sets a Postgres session variable on every operation, and row-level-security policies on every tenant_id table read that variable independently.
Key decision
RLS as a genuine second line of defence, not a formality alongside application-level scoping
Result
A forgotten tenant filter fails closed — returns zero rows instead of leaking another restaurant's orders — verified by a dedicated RLS smoke test
Draft/Publish Menu Revisions
Menu content hangs off a menu_revision, never off the menu record itself. Editing writes to a DRAFT; publishing deep-clones it — every section, item, variant, price, allergen, diet tag and formule course — into an immutable PUBLISHED snapshot inside one transaction, preserving each row's stable ref so translations and analytics keep resolving across versions.
Key decision
Snapshot-on-publish over live in-place editing
Result
Diners never see a half-edited menu, and cache invalidation becomes a key change rather than a purge
Per-Table QR With Server-Side Scan Resolution
Every table gets its own QrCode row and short code, rendered server-side as SVG via the qrcode package — no third-party QR service. Scanning hits a public scan-resolve endpoint that records a ScanEvent and silently carries the resolved tableId into the ordering flow.
Key decision
Per-table QR + scan-resolve over a single restaurant-wide QR with a manually typed table number
Result
Every order is automatically and reliably tagged to the table it came from; the diner never types anything
One Next.js App, Three Personas
Rather than the originally scoped three-surface split (Next.js diner PWA, React back-office, Flutter merchant app), a single Next.js App Router project serves three route groups from one deployable: anonymous diner ordering, tenant-scoped restaurant admin, and platform-admin-only cross-tenant console — each gated client-side and re-enforced server-side by the API.
Key decision
Collapse three planned frontends, plus a native merchant app, into one web app for the MVP
Result
The full loop — sign up, build a menu, generate table QRs, scan, order, see it in the admin queue tagged to the right table — built and verified with real Playwright runs against a live Postgres/Redis backend
Measurable impact
56
Domain model tables, 22 enums, covering every aggregate from tenancy through billing
2×
Tenant isolation enforced independently at the ORM layer and in Postgres row-level security
108
Real dishes across 18 sections (plus 3 formules) digitized from a corrupted restaurant scan and loaded as seed data
4 days
From finalized specification to a working three-persona MVP, verified end-to-end against a live backend
Tech stack
What we learned
This project showed that a defensible restaurant-ordering SaaS isn't won on the QR code — every part of that pitch is commoditised in France. It's won on the parts competitors treat as an afterthought: compliance-grade allergen data instead of a free-text field, an ingestion pipeline that survives a real corrupted phone scan instead of a clean demo PDF, formules modelled as real orderable structure instead of a text blob, and tenant isolation that fails closed instead of leaking. Designing the full long-range domain model before writing the first controller meant the MVP and every later phase land on the same schema with zero rework.
- 01
Modelling the whole long-range domain up front costs nothing when the MVP only touches part of it, and saves a re-migration later
- 02
Two independent layers of tenant isolation is the right amount for a multi-tenant SaaS — one alone has a known bypass
- 03
The unglamorous data problem (a corrupted paper-menu scan) is the actual product moat, not the QR code or the menu UI
- 04
Collapsing three planned frontends into one Next.js app with route-group-level auth shipped a working three-persona loop in days, not the weeks a three-surface build would have taken
Want a system like this one?
Bring us the problem behind it - the constraint, the load, the deadline. You get an architecture opinion and a cost view from the engineers who built the work on this page, not a sales team.
Prefer we come to you? Fill in the form below.
Tell us where to reach you
Takes under a minute. An engineer reads it, not a lead-scoring tool.
More case studies
Ready to build something that matters?
We solve problems that don't have Stack Overflow answers. Let's talk.
Book a Discovery Call