Skip to content

SupremShrestha/Restaurant-SAAS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TableFlow

A multi-tenant restaurant SaaS POS system. Each restaurant gets its own isolated subdomain, JWT-scoped auth, and a live order board. Customers scan a QR code at their table, order with no login, and the order flows in real time through Kitchen → Waiter → Cashier dashboards via WebSocket.

Built as a solo portfolio project targeting software development and full-stack internship applications.


Tech Stack

Layer Technology
Frontend React 18, Vite, React Router v6, @stomp/stompjs
Backend Spring Boot 4.x, Java 25, Spring Security, Spring Data JPA
Database PostgreSQL 16 (Docker)
Real-time WebSocket (STOMP over native WebSocket)
Auth JWT (jjwt), BCrypt password hashing
Dev environment Fedora Linux, Docker Compose

Features

Multi-tenant architecture

  • Each restaurant gets a unique subdomain (everest.yourapp.com)
  • All restaurants share one database — tenant isolation enforced via restaurant_id scoping on every query
  • Cross-tenant token rejection: a JWT from Restaurant A is rejected on Restaurant B's subdomain
  • TenantResolutionFilter resolves the active tenant from the Host header on every request

Order pipeline (real-time)

  • Customers scan a QR code at their table — no login required
  • Order moves through a state machine: PLACED → PREPARING → READY → SERVED → PAID
  • Every status change broadcasts instantly to all connected dashboards via WebSocket (STOMP)
  • Separate role-gated dashboards for Kitchen, Waiter, and Cashier

Role-based access control

  • Five roles: PLATFORM_ADMIN, ADMIN, KITCHEN, WAITER, CASHIER
  • JWT carries role and restaurant ID — validated on every request
  • Role-gated status transitions: Kitchen can only move PLACED→PREPARING and PREPARING→READY; Waiter can only mark SERVED; Cashier can only mark PAID
  • Deleted users are force-logged-out within seconds (DB existence check on every authenticated request)

Restaurant admin panel

  • Menu management: add/disable/enable/delete items
  • Staff management: create/remove Kitchen, Waiter, Cashier accounts with role badges
  • Table management: create tables, each gets a UUID-based QR token, QR code rendered inline

Subscription system

  • Every restaurant has a subscription record (ACTIVE, GRACE_PERIOD, BLOCKED)
  • SubscriptionFilter enforces blocking — blocked restaurants get 402 Payment Required on all API calls except menu browsing
  • Platform admin dashboard: list all restaurants, view subscription status, manually block/unblock/set grace period

Onboarding

  • Public signup flow: restaurant name, subdomain (validated, unique), admin credentials
  • Creates restaurant record, admin user, and 30-day ACTIVE subscription in one transaction
  • Redirects to the new restaurant's subdomain admin panel already logged in

Architecture

Browser (React/Vite)
    │
    ├── Customer: /menu?table=<qrToken>&tenant=<subdomain>
    │       └── Scans QR → views menu → places order (no login)
    │
    ├── Staff dashboards: /kitchen, /waiter, /cashier
    │       └── JWT auth → WebSocket subscription → live order updates
    │
    └── Admin: /admin
            └── JWT auth (ADMIN role) → menu/staff/table management

Spring Boot Backend (port 8080)
    │
    ├── TenantResolutionFilter → resolves restaurant_id from Host header or ?tenant= param
    ├── JwtAuthFilter          → validates JWT, sets Spring Security context
    ├── SubscriptionFilter     → blocks requests for BLOCKED restaurants (402)
    │
    ├── REST API
    │   ├── POST /api/onboard              → restaurant signup (public)
    │   ├── POST /api/auth/login           → returns JWT (public)
    │   ├── GET/POST/DELETE /api/menu-items → menu CRUD (GET public, mutations ADMIN)
    │   ├── GET/POST /api/orders           → order list + creation (GET staff, POST public)
    │   ├── PATCH /api/orders/{id}/status  → role-gated status transitions
    │   ├── /api/tables/**                 → table management (ADMIN)
    │   ├── /api/auth/users                → staff management (ADMIN)
    │   └── /api/platform/**               → platform admin (PLATFORM_ADMIN)
    │
    └── WebSocket /ws (STOMP)
            └── /topic/restaurant/{id}/orders → broadcasts on every order create/update

PostgreSQL
    ├── restaurants   (id, name, subdomain)
    ├── users         (id, restaurant_id, name, email, password_hash, role)
    ├── menu_items    (id, restaurant_id, name, price, available)
    ├── tables        (id, restaurant_id, table_number, qr_token)
    ├── orders        (id, restaurant_id, table_id, status, total_amount, created_at)
    ├── order_items   (id, order_id, menu_item_id, menu_item_name, quantity, price_at_order_time)
    └── subscriptions (id, restaurant_id, status, current_period_end, last_payment_date)

Running Locally

Prerequisites

  • Java 21+ (project uses Java 25)
  • Node.js 18+
  • Docker + Docker Compose

1. Start Postgres

docker compose up -d

Postgres runs on port 5433 (not the default 5432, to avoid conflicts with any native install).

2. Start the backend

cd backend
./mvnw spring-boot:run

Backend runs on http://localhost:8080.

3. Start the frontend

cd frontend
npm install
npm run dev

Frontend runs on http://localhost:5173.

4. Subdomain routing (local)

For subdomain routing to work locally (everest.localhost:5173), most modern browsers resolve *.localhost automatically. If yours doesn't, add entries to /etc/hosts:

127.0.0.1 everest.localhost

5. Create your first restaurant

Visit http://localhost:5173 → "Create your restaurant" → fill in the signup form. You'll be redirected to your restaurant's admin panel at http://<subdomain>.localhost:5173/admin.


Environment Variables

Variable Default (dev) Description
JWT_SECRET hardcoded dev string Secret key for JWT signing — must be overridden in production
spring.datasource.password changeme Postgres password

Known Limitations / Production Hardening Required

  • JWT secret is hardcoded in application.properties as a dev fallback — set JWT_SECRET env var before any real deployment
  • JWTs are stored in localStorage — vulnerable to XSS; httpOnly cookies would be more correct for production
  • WebSocket uses ws:// — must become wss:// once served over HTTPS
  • Nginx requires explicit WebSocket upgrade config (Upgrade / Connection headers) when used as a reverse proxy
  • In-memory STOMP broker — works for a single instance; switch to Redis/RabbitMQ for horizontal scaling
  • No automated subscription billing — payment marking is manual via platform admin

Project Status

Phase Status
Phase 0 — Environment setup ✅ Complete
Phase 1 — Core order loop (QR → Kitchen → Waiter → Cashier → Paid) ✅ Complete
Phase 2 — Auth + role-based access ✅ Complete
Phase 3 — Multi-tenancy + onboarding ✅ Complete
Phase 4 — Subscriptions + platform admin ✅ Complete
Phase 5 — Polish + demo prep 🔶 In progress

Restaurant-SAAS

About

TableFlow — a multi-tenant restaurant SaaS POS system with subdomain-isolated tenants, JWT-scoped auth, and real-time order tracking across Kitchen, Waiter, and Cashier dashboards via WebSocket.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors