NexTicket — A Multi-Tenant SaaS Platform for Restaurant Operations, Built with Elixir & Phoenix

hero


Running a restaurant must be chaotic. Orders get lost between the counter and the kitchen. Waiters waste time walking back and forth. Customers wait too long to place an order. And if you're managing more than one location, multiply that chaos by ten.

NexTicket is a real-time restaurant management platform I built from scratch with Elixir, Phoenix LiveView, and PostgreSQL — designed to eliminate the friction between customers, counter staff, and the kitchen.

In this article, I'll walk you through the architecture, the technical decisions, and the lessons learned while building it.


The Problem

Most restaurant management tools fall into one of two camps: overpriced enterprise software with features no one uses, or fragile point-of-sale terminals that crash during the dinner rush.

I wanted something different:

  • Customers scan a QR code at their table and order from their own phone — no app download required
  • Counter staff see every table's status in real-time and validate incoming orders
  • Kitchen staff get a live display of what to prepare and in what order
  • Restaurant owners manage their menu, tables, and staff from one dashboard
  • Everything updates instantly — no page refreshes, no polling

client_menu


How It Works

The order flow is designed around how restaurants actually operate:

  1. A customer sits down and scans the QR code on their table
  2. The counter is instantly notified — "Table 5 has guests"
  3. Counter staff activates the session and the customer can start browsing the menu
  4. The customer browses the menu, customizes items, and places an order — all from their phone
  5. The counter reviews and confirms the order, then sends it to the kitchen
  6. The kitchen prepares the food using a live Kitchen Display System (KDS)
  7. When it's ready, the kitchen marks it done — the counter is notified to deliver
  8. Order complete

Every step happens in real-time. No paper tickets. No shouting across the kitchen.

counter_view


Architecture: Multi-Tenancy with PostgreSQL Schemas

NexTicket is a true multi-tenant SaaS platform. Each restaurant gets its own isolated PostgreSQL schema, not just a tenant_id column on every table.

┌──────────────────────────────┐
│        Public Schema         │
│  accounts, tenants, plans,   │
│  subscriptions               │
└──────────────────────────────┘

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ tenant_      │  │ tenant_      │  │ tenant_      │
│ pats_pizza_  │  │ sushi_bar_   │  │ cafe_luna_   │
│ a7b3c9d2     │  │ f2e8d1a4     │  │ c5b7e3f1     │
│              │  │              │  │              │
│ staff, menu, │  │ staff, menu, │  │ staff, menu, │
│ tables,      │  │ tables,      │  │ tables,      │
│ orders, ...  │  │ orders, ...  │  │ orders, ...  │
└──────────────┘  └──────────────┘  └──────────────┘

Why schemas instead of row-level tenancy?

AspectSchema IsolationRow-Level (tenant_id)
Data isolationComplete — impossible to query another tenant's dataApplication-level — one missing WHERE clause = data leak
PerformanceFast — no JOIN on tenant_id for every querySlower — every query needs tenant filtering
ComplianceSchema-level security boundariesRelies on application correctness
Per-tenant backupsbackup individual schemas

Each restaurant is identified by its subdomain. A request to pats-pizza.nexticket.com automatically sets the database prefix to the correct tenant schema. All subsequent queries are scoped — there's no way to accidentally leak data between restaurants.


Real-Time, Everywhere: Phoenix PubSub

The magic of NexTicket's real-time experience comes from Phoenix PubSub combined with LiveView.

When a customer places an order, the counter dashboard updates instantly. When the kitchen marks a dish as ready, the counter sees it immediately. No WebSocket boilerplate — Phoenix handles it all.

But in a multi-tenant system, you need to be careful: if Restaurant A creates an order, Restaurant B's kitchen shouldn't see it. Every PubSub topic is prefixed with the tenant's schema name:

tenant_pats_pizza_a7b3c9d2:orders
tenant_sushi_bar_f2e8d1a4:orders

This ensures complete message isolation between tenants while still leveraging Phoenix's lightweight PubSub system.

kitchen_view


The Customer Experience

When a customer scans the QR code at their table, they land on a clean, mobile-first menu — no app to download, no account to create.

They can:

  • Browse categories with smooth scrolling navigation
  • View item details with full-screen images and descriptions
  • Build a cart with per-item notes ("no onions", "extra spicy")
  • Add order-level notes ("birthday dinner — surprise cake at the end")
  • Track their order status in real-time as it moves through the kitchen

The entire experience is a single Phoenix LiveView — no page loads, no spinners.

client_item_modalclient_reviewclient_orders


The Admin Dashboard

Restaurant owners get a full management interface:

  • Menu Management — Categories and items with drag-and-drop ordering, image uploads, and pricing
  • Table Management — Create tables, auto-generate QR codes, monitor active sessions
  • Staff Management — Role-based access control (Admin, Staff, Kitchen, Counter)
  • Order History — Paginated order history with search and filtering
  • Sales Reports — Revenue tracking and popular item analytics
  • Audit Trail — Every action is logged — who changed what, and when

dashboard


Technical Highlights

Performance & Caching

Frequently accessed data (like menu categories and items) is cached with Cachex, using tenant-scoped cache keys. When a menu item is updated, only the relevant cache entry is invalidated — not the entire menu.

Background Jobs

Oban handles background processing — daily sales report emails, session cleanup, and subscription management. Each job carries the tenant context so it can set the correct database schema before executing.

Security

  • 15-minute idle session timeout for staff workstations
  • Role-based access control at both the router and LiveView level
  • Rate limiting on registration and authentication endpoints
  • Complete audit trail for all destructive operations
  • CSRF protection and XSS prevention built into Phoenix

Subscription System

A flexible subscription tier system with configurable feature limits:

FeatureStarterProfessionalEnterprise
Tables1050Unlimited
Menu Items50200Unlimited
Staff Users310Unlimited
Order History30 days365 daysUnlimited
Advanced Analytics
Custom Branding

The Tech Stack

LayerTechnology
LanguageElixir 1.15+
FrameworkPhoenix 1.8 with LiveView
DatabasePostgreSQL 16
Multi-tenancyPostgreSQL schemas
Background JobsOban
CachingCachex
File StorageWaffle (local dev) / S3 (production)
QR Codesqr_code
PaginationFlop
StylingTailwind CSS 4

Lessons Learned

Schema-based multi-tenancy is powerful but requires discipline. Every background job, every PubSub subscription, every cache key needs to be tenant-aware. Miss one, and you'll get silent data leaks or cross-tenant messages.

LiveView changes how you think about UIs. There's no REST API, no frontend framework, no state management library. The server is the state. It's remarkably productive once you embrace it.

Component extraction matters. The counter dashboard started as a 1,650-line LiveView. Breaking it into focused components (session cards, order cards, notification panels) made it maintainable without sacrificing the real-time experience.

Elixir's concurrency model is a perfect fit for restaurant ops. Each table session, each order, each kitchen display — they're all independent processes that need to communicate. That's exactly what the BEAM was built for.


What's Next

NexTicket is feature-complete for its core use case. The roadmap includes:

  • Translations
  • Payment integration (Stripe) for subscription billing
  • Production deployment on Fly.io with wildcard subdomain support
  • Advanced analytics with historical trend reporting
  • Multi-location support for restaurant chains
  • More customization for the client