QuickMenu is a full-stack, real-time restaurant management platform designed to eliminate the friction between customers and service staff. Built with a modular monorepo architecture, it showcases modern engineering practices in Spring Boot (Java 21) and React 19.
Version: 1.0 Status: Production / Demo Ready
Maintainer: Sohail
License: MIT
- ๐ Project Overview
- ๐๏ธ System Architecture
- โ๏ธ Backend Ecosystem (Spring Boot)
- ๐จ Frontend Ecosystem (React + Vite)
- ๐พ Database & Data Integrity
- ๐ API Reference
- ๐ Deployment & Operations
- ๐ฎ Future Roadmap & Technical Debt
Traditional dining experiences often suffer from friction points:
- Wait Times: Customers waiting for menus, waiters to take orders, or the bill.
- Communication Gaps: Difficulty signalling staff in busy or loud environments.
- Static Content: Printed menus cannot reflect real-time availability or price changes.
- Operational Blindness: Owners lack real-time visibility into current service load or instantaneous revenue.
QuickMenu is a "Phygital" (Physical + Digital) solution that bridges the gap using QR codes. It is a full-stack platform that empowers:
- Customers to self-serve (Scan -> Order -> Eat).
- Staff to receive instant table alerts (Orders, Bell rings).
- Admins to manage multiple restaurants and view aggregate analytics.
| Feature | Role | Description | Tech Implementation |
|---|---|---|---|
| Scan-to-Order | Customer | Instant menu access without app download. | Dynamic Routing react-router, URL params |
| Real-time Order Feed | Staff | Orders appear instantly on kitchen dashboard. | WebSocket, STOMP over SockJS |
| Digital Bell | Customer | "Call Waiter" button with rate limiting. | Redis (planned) / ConcurrentHashMap (current) |
| Menu Management | Admin | Create dishes, categories, upload images. | Cloudinary SDK, Spring Data JPA |
| Analytics Dashboard | Admin | Visual charts of revenue and item popularity. | Recharts, Native SQL Aggregation |
| Demo Mode | Public | Auto-resetting data for recruiters. | Spring @Scheduled Task, Soft Deletes |
The system follows a classic Client-Server model but enhanced with Event-Driven capabilities for real-time features.
graph TD
UserPhone[User Smartphone]
StaffTablet[Staff Tablet]
AdminLaptop[Admin Laptop]
subgraph Load Balancer / Gateway
Nginx[Reverse Proxy / TLS Termination]
end
subgraph Application Core
FE[Frontend SPA React]
BE[Backend API Spring Boot]
end
subgraph Data & Services
DB[(PostgreSQL Database)]
Img[Cloudinary CDN]
Mail[SendGrid Email Service]
end
UserPhone -->|HTTPS/WSS| Nginx
StaffTablet -->|HTTPS/WSS| Nginx
AdminLaptop -->|HTTPS/WSS| Nginx
Nginx --> FE
Nginx --> BE
FE -->|REST API| BE
FE -->|STOMP/WS| BE
BE -->|JDBC| DB
BE -->|HTTP| Img
BE -->|SMTP| Mail
The architecture follows a decoupled, event-driven-ready design, ensuring high availability and low latency for critical restaurant operations.
graph TD
subgraph "Frontend Layer (React + Vite)"
Landing[Landing Site]
CustomerUI[QR-Menu & Cart]
AdminApp[Admin Analytics & CMS]
StaffWS[Real-time Staff Dashboard]
end
subgraph "Logic Layer (Spring Boot 3.5)"
Gateway[Spring Security / JWT Gate]
WS_Broker[STOMP/WebSocket Broker]
OrderOrch[Order Orchestrator]
BellMgr[Bell Notification Engine]
MetricsEngine[PostgreSQL Analytics]
Scheduler[Demo Reset Worker]
end
subgraph "Storage & Infrastructure"
PG_DB[(PostgreSQL)]
H2_DB[(H2 In-Memory - Dev)]
CDN[(Cloudinary Image Storage)]
Mail[SendGrid / JavaMail]
end
CustomerUI -->|REST / HTTPS| Gateway
CustomerUI -->|STOMP| WS_Broker
StaffWS <-->|Full Duplex| WS_Broker
Gateway --> OrderOrch
Gateway --> BellMgr
OrderOrch --> PG_DB
MetricsEngine --> PG_DB
OrderOrch --> WS_Broker
BellMgr --> WS_Broker
-
Web Client (Single Page Application)
- Tech: React 19, Vite, TypeScript, TailwindCSS.
- Responsibility: Rendering UI, handling user interactions, maintaining local session state (JWT), establishing WebSocket connections.
- Deploy Target: Vercel / Netlify (Static Hosting).
-
API Server
- Tech: Java 21, Spring Boot 3.5.0.
- Responsibility: Business logic, authentication, data validation, websocket message brokerage, scheduled maintenance.
- Deploy Target: Render / Railway / AWS EC2.
-
Database
- Tech: PostgreSQL 15 (Production), H2 (Development).
- Responsibility: Persistent storage of relational data (Users, Restaurants, Orders).
The project is structured as a monorepo to keep full-stack context in one place, easing development and refactoring.
QuickMenu/
โโโ backend/ # Spring Boot Application
โ โโโ src/main/java/com/quickmenu/
โ โ โโโ admin/ # Admin Analytics & Dashboard Logic
โ โ โโโ auth/ # JWT, User, Security Config
โ โ โโโ bell/ # Bell/Notification Feature
โ โ โโโ config/ # Global Config (CORS, Swagger, WS)
โ โ โโโ menu/ # Restaurant, Dish, Category domains
โ โ โโโ orders/ # Order processing & State machine
โ โ โโโ scheduler/ # Cron jobs (Demo Reset)
โ โโโ pom.xml # Maven Dependencies
โ โโโ .env.example # Backend Environment Template
โ
โโโ frontend/ # React Application
โ โโโ src/
โ โ โโโ app/ # Zustand Store definitions
โ โ โโโ components/ # Reusable UI Blocks (Buttons, Modals)
โ โ โโโ lib/ # Utilities (API client, Date formatting)
โ โ โโโ pages/ # Route Views (Admin, Menu, Auth)
โ โ โโโ routes/ # Route Definitions
โ โโโ package.json # Node Dependencies
โ โโโ vite.config.ts # Build Configuration
โ
โโโ infra/ # Infrastructure / Docker / K8s (Future)
The backend is built with Robustness and Scalability in mind, leveraging the latest features of Java 21 and Spring Boot 3.5.
- Spring Boot 3.5.x: Utilizing the latest auto-configuration capabilities.
- Java 21: Leveraging features like
Recordsfor DTOs and Pattern Matching forinstanceofchecks. - Lombok: Reducing boilerplate code (Getters, Setters, Builders, Slf4j).
Security is implemented using a stateless JWT (JSON Web Token) architecture. This ensures that the backend can scale horizontally without sticky sessions.
- Request Arrival: Every HTTP request passes through the
SecurityFilterChain. - Public Endpoints:
/api/auth/**, GET/api/{rid}/menu, and WebSocket endpoints are whitelisted using.permitAll(). - JWT Filter (
JwtAuthenticationFilter):- Extracts the
Authorization: Bearer <token>header. - Validates the signature using the secret key.
- Parses claims (User ID, Role, Email).
- Creates a
UsernamePasswordAuthenticationTokenand places it in theSecurityContextHolder.
- Extracts the
- Authorization: Endpoints annotated with
@PreAuthorize("hasRole('ADMIN')")are checked against the context authorities. - Exception Handling: Custom
AuthenticationEntryPointreturns standard JSON 401/403 errors instead of default HTML pages.
Code Highlight: Stateless Session Policy
http.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);QuickMenu uses WebSockets to push updates to the Staff Dashboard immediately.
- Broker: Simple In-Memory Broker active on
/topicand/queue. - Endpoints:
/ws: SockJS fallback endpoint (for browsers)./websocket: Raw WebSocket endpoint (for debug tools like Postman).
- Allowed Origins: Configured to allow cross-origin connections from the frontend domain.
- Frontend Subscribe: Staff client subscribes to
/topic/restaurants/{id}/orders. - Event Trigger: Customer places order via POST
/api/{rid}/orders. - Processing:
OrderServicesaves order to DB. - Publish:
SimpMessagingTemplate.convertAndSend()pushes the order DTO to the topic. - Reception: All connected staff clients receive the JSON payload and update the React state.
The heart of the application. It handles the lifecycle of an order: PLACED -> PREPARING -> READY -> SERVED -> PAID.
Critical Logic: Concurrency Control To prevent two customers from booking the same last-available item or table simultaneously (though strictly tables here), we use Pessimistic Locking.
// Logic inside placeOrder transaction
TableEntity table = tableRepository.findByIdForUpdate(req.getTableId())
.orElseThrow(() -> new IllegalArgumentException("Invalid table"));
if (table.getOccupied()) {
throw new TableOccupiedException("Table is already busy!");
}
table.setOccupied(true);Why Pessimistic? For a restaurant table, correctness (preventing double seating) is more critical than raw throughput.
Allows customers to signal staff.
Rate Limiting Strategy: To prevent a child from spamming the bell button and flooding the staff dashboard:
- Mechanism:
ConcurrentHashMap<String, Instant> lastEventAt - Key:
restaurantId:tableId - Logic: If
now()is less thanlastEvent + 20 seconds, the request is rejected with429 Too Many Requests. - Trade-off: In-memory map clears on server restart. For persistent rate limiting across multiple server instances, we would move this to Redis.
- Spring Data JPA: Repository interfaces for standard CRUD.
- Specifications: Used for complex dynamic filtering (e.g., finding orders by date range AND status AND search term).
- Database:
- Dev: H2 In-Memory (auto-creates tables).
- Prod: PostgreSQL (schema validation).
The frontend is a modern, responsive SPA designed for mobile-first usage (Customers) and desktop-first usage (Admin/Staff).
- Vite: Chosen for its lightning-fast HMR (Hot Module Replacement) and optimized build times compared to CRA/Webpack.
- TypeScript: Essential for maintaining sanity in a codebase with complex data models (Orders, Dishes, Auth).
- TailwindCSS: Utility-first styling allowing rapid UI development without context switching to CSS files.
Components are split into Atomic (buttons, inputs) and Molecular (cards, forms) structures.
pages/: Route handlers (e.g.,RestaurantMenu.tsx,AdminDashboard.tsx).components/: Reusable blocks.DishCard.tsx: Displays image, price, title.CartFloating.tsx: The sticky cart bar for mobile users.OrderSummaryModal.tsx: The checkout experience.
We chose Zustand over Redux Toolkit or Context API.
- Why?
- Simplicity: No boilerplate (reducers, actions, providers).
- Performance: Selectors allow components to subscribe to only specific slices of state.
- Decoupling: State logic (
useAuthStore) is separate from UI components.
Store: useAuthStore.ts
Manages the JWT token and decodes it to know the current user's role.
// Auto-decoding logic on token set
setToken: (token) => {
if (token) {
localStorage.setItem('qm_token', token);
const decoded = jwtDecode(token); // Custom decode utility
set({ token, user: decoded });
}
}Since the demo runs on a free-tier hosting (Render) that spins down inactive instances:
- Detection: If an API call fails or times out initially.
- UI Feedback: A friendly "Waking up server..." animation appears.
- Polling: The frontend doesn't aggressively poll but encourages the user to wait while the backend cold-starts.
In BellButton.tsx:
- User clicks "Ring Bell".
- UI immediately shows "Sending...".
- On success, button turns yellow/green.
- On rate-limit (429), it shows a specific "Wait a moment" error message.
- Restaurant (1) โ (N) Table
- Restaurant (1) โ (N) Category
- Category (1) โ (N) Dish
- Restaurant (1) โ (N) Order
- Order (1) โ (N) OrderItem
- Table (1) โ (N) BellEvent
To preserve data integrity (accounting, history) even when items are "deleted":
- Backend: Entities have a
deletedAttimestamp. - Hibernate: Global
@Where(clause = "deleted_at IS NULL")ensures "deleted" items are invisible to normal queries. - Admin: Specific "Include Deleted" queries can be written if needed for audit logs.
A unique feature for a portfolio project.
- Problem: Recruiters/Users constantly modify data (delete dishes, change names), ruining the demo for the next person.
- Solution:
DemoDataScheduler.java - Interval: Every 30 minutes.
- Action:
- Finds all entities belonging to the "Demo Restaurant".
- Resets their names/prices to defaults.
- Restores soft-deleted items.
- Cleans up old "test" orders to keep the dashboard snappy.
Base URL: /api
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /auth/signup |
Register new user (Admin/Customer) | No |
| POST | /auth/login |
Login and retrieve JWT | No |
| POST | /auth/forgot-password |
Initiate password reset email | No |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /restaurants |
List all restaurants | No |
| POST | /restaurants |
Create a new restaurant | Admin |
| PATCH | /restaurants/{id} |
Update settings (Currency, Timezone) | Admin |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /{id}/dishes |
Get full menu (grouped by category) | No |
| POST | /{id}/dishes |
Add new dish | Admin |
| PATCH | /{id}/dishes/{did}/availability |
Toggle In-Stock/Out-of-Stock | Staff/Admin |
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /{id}/orders |
Place a new order | No (Public Table QR) |
| GET | /{id}/orders |
Staff Order Feed (WebSocket backed) | Staff |
| PATCH | /{id}/orders/{oid} |
Update Status (Serving/Served/Paid) | Staff |
- JDK 21
- Node.js 18+
- Maven
cd backend
# Optional: Edit src/main/resources/application.yml for DB credentials if not using H2
./mvnw clean spring-boot:run
# Server starts at http://localhost:8080cd frontend
# Create .env file
echo "VITE_API_URL=http://localhost:8080" > .env
npm install
npm run dev
# App starts at http://localhost:5173./mvnw package -DskipTests
java -jar target/quickmenu-0.0.1-SNAPSHOT.jarNote: Ensure SPRING_PROFILES_ACTIVE=prod is set to use PostgreSQL.
npm run build
# Serve the /dist folder using Nginx, Vercel, or S3| Variable | Default | Description |
|---|---|---|
SPRING_DATASOURCE_URL |
jdbc:h2:mem:db | Database URL (Postgres in prod) |
APP_JWT_SECRET |
(random) | 256-bit Key for signing tokens |
CLOUDINARY_URL |
- | Image upload credentials |
SENDGRID_API_KEY |
- | Email service key |
| Variable | Description |
|---|---|
VITE_API_URL |
Backend API Base URL (e.g., https://api.quickmenu.com) |
While QuickMenu is feature-complete for a V1, we have identified areas for evolution.
- Current:
OrderServicehandles everything. - Future: Split
NotificationService(Bell/Email) into a separate microservice listening to Kafka topics. This allows the notification system to scale independently of the ordering traffic.
- Current: Database hits for every menu load.
- Future: Implement
Rediscache forGET /menu. Menu data changes rarely but is read frequently (Read-Heavy workload).
- Current: In-memory rate limiting.
- Future: Distributed rate limiting using Redis (Token Bucket algorithm) to protect against DDoS attacks on the Bell API.
- Current: SQL Aggregation on the live transactional DB.
- Future: ETL pipeline to move completed orders to a Data Warehouse (Snowflake/BigQuery) for expensive queries, keeping the OLTP database lean.
QuickMenu โ Bridging the gap between the kitchen and the customer.