Skip to main content
Multi-Tenant SaaS

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

01

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

02

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

03

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

04

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

05

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

NNestJSNNext.jsRReactPPostgreSQL 16PPrismaRRow-Level SecurityRRedisBBullMQJJWT (Passport)AArgon2idQqrcodeTTailwind CSSTTypeScript

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.

Required. We reply within one business day - no newsletter, no sequence.

Ready to build something that matters?

We solve problems that don't have Stack Overflow answers. Let's talk.

Book a Discovery Call