Skip to content
UA.
All projects
LiveSaaSSep 2026 — Present

ArMenu

Multi-tenant QR menus with in-browser AR — and no app to install.

The ArMenu owner dashboard beside the guest app showing a burger in 3D with an AR button.
My role
Solo developer — API, both React apps, the 3D asset pipeline, delivery and observability.
Status
Live

The problem

A printed menu cannot show a guest what a dish looks like, cannot be read in their language, and cannot be changed without a reprint. Every digital answer to that asks the guest to install something, which at a restaurant table is where the idea dies.

What I built

A multi-tenant B2B SaaS. A guest scans the QR code on the table, reads the menu in their own language, and places a dish on the table in augmented reality through model-viewer — in the browser they already have open. Behind it: a .NET 10 API over PostgreSQL, and a separate Node service that turns an uploaded 3D model into optimised GLB, USDZ and poster images.

What it looks like

  • The guest menu on a phone: search, filters, categories and a dish carrying a 3D badge.
    What the guest sees after scanning the QR code on the table.
  • A dish opened on a phone, rendered in 3D, with a 'See it on your table' button.
    The 3D stack loads only when a dish with a model is opened — guests who never use AR never pay for it.
  • The dashboard's QR screen with a menu link, SVG and PNG downloads and printable per-table cards.
    Per-table QR cards, printed from the dashboard — the table number travels with the scan.

Architecture

  1. Guest & dashboard apps

    Two static apps on a CDN. The guest app has to open instantly on mobile data; the dashboard is where owners edit their own menus.

    • React 19
    • TanStack Router + Query
    • Vite
    • React Aria
  2. API

    Every rule in one place, behind a committed OpenAPI contract that generates the clients' TypeScript types.

    • .NET 10
    • Minimal APIs
    • CQRS
    • JWT per tenant
  3. Persistence

    Tenant isolation enforced by the database itself, not by remembering to filter.

    • PostgreSQL 18
    • EF Core 10
    • Row-level security
  4. Asset pipeline

    Turns an uploaded model into Meshopt + WebP GLB, a USDZ for iOS Quick Look, and poster images.

    • Node.js
    • glTF Transform
    • meshoptimizer
    • headless Chrome

Decisions & trade-offs

  • Row-level security in the database, not in the query layer

    A tenant filter that lives in application code is one forgotten WHERE clause away from showing one restaurant another's menu. Pushing isolation into Postgres makes the leak impossible rather than unlikely.

  • Clean Architecture enforced by a test, not by discipline

    Architecture tests fail the build if a source dependency points outward. A layering rule that is only written in a README is a layering rule that erodes.

  • WebAR instead of a native app

    A diner standing at a table will not install an app to look at a menu. model-viewer loads on demand, so the guests who never open AR never pay for it.

  • A separate service for 3D assets

    Mesh optimisation is slow and CPU-bound. Keeping it out of the API means one large upload can never make a menu request wait behind it.

Outcome

Restaurants change their own menus; guests see a dish before they order it. Neither side installs anything.

Measured

Lighthouse (perf / a11y / BP / SEO)
98 / 100 / 100 / 100
Total blocking time · layout shift
0 ms · 0
Startup JavaScript (gzip)
117 KiB
3D stack, loaded on demand (gzip)
291 KiB