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.
Ready to build something that matters?
We solve problems that don't have Stack Overflow answers. Let's talk.
Book a Discovery Call