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

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

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