A complete restaurant ordering system with multi-tenant architecture, supporting multiple cafes with role-based access control.
- Overview
- Features
- Architecture
- Frontend Setup
- Backend Setup
- Database Schema
- API Endpoints
- Multi-Tenant Architecture
- Authentication & Authorization
- Environment Variables
- Scripts
- Deployment
ArtHaus CafΓ© is a modern restaurant ordering system built with:
- Frontend: React, Vite, Redux Toolkit, GSAP animations
- Backend: Node.js, Express, better-sqlite3
- Database: SQLite with multi-tenant support
- Authentication: JWT, Google OAuth
- State Management: Redux with persistence
- QR code-based table ordering
- Real-time menu browsing
- Cart management
- Order tracking
- Google Sign-In
- Profile management
- Multi-tenant support (multiple cafes)
- Role-based access control (Super Admin, Cafe Admin, Staff)
- User management
- Order management
- Menu management
- Analytics dashboard
- Multi-tenant architecture with subdomain routing
- Redux state persistence
- Optimistic UI updates
- Responsive design
- GSAP animations
- RESTful API
order/
βββ client/ # Frontend (React + Vite)
β βββ src/
β β βββ components/ # React components
β β βββ store/ # Redux store & slices
β β βββ services/ # API services
β β βββ utils/ # Utility functions
β β βββ hooks/ # Custom hooks
β βββ public/ # Static assets
βββ server/ # Backend (Node.js + Express)
β βββ src/
β β βββ controllers/ # Route controllers
β β βββ models/ # Database models
β β βββ routes/ # API routes
β β βββ middleware/ # Express middleware
β β βββ database/ # Database connection & schema
β β βββ utils/ # Utility functions
β β βββ config/ # Configuration
β βββ data/ # SQLite database files
β βββ scripts/ # Utility scripts
βββ README.md
- Node.js 18+
- npm or yarn
cd client
npm installCreate .env file in client/:
# API Configuration
VITE_API_URL=http://localhost:4002/api/v1
# Google OAuth
VITE_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
# App Config
VITE_APP_NAME=ArtHaus CafΓ©
VITE_APP_VERSION=1.0.0npm run devFrontend runs on http://localhost:5173
npm run build- Node.js 18+
- better-sqlite3
cd server
npm installCreate .env file in server/:
# Server Configuration
NODE_ENV=development
PORT=4002
API_VERSION=v1
# Database
DB_PATH=./data/arthaus.db
DB_BUSY_TIMEOUT=5000
# Security
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=12
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# CORS
CORS_ORIGIN=http://localhost:5173,http://localhost:3000
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
# Logging
LOG_LEVEL=info
LOG_FILE=./logs/app.lognpm run devBackend runs on http://localhost:4002
CREATE TABLE restaurants (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
subdomain TEXT UNIQUE NOT NULL,
domain TEXT,
logo_url TEXT,
theme_config TEXT,
settings TEXT,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT UNIQUE NOT NULL,
restaurant_id INTEGER,
email TEXT,
phone TEXT,
password_hash TEXT NOT NULL,
name TEXT NOT NULL,
role TEXT DEFAULT 'customer' CHECK(role IN ('customer', 'staff', 'admin', 'super_admin')),
avatar_url TEXT,
avatar_base64 TEXT,
google_id TEXT,
facebook_id TEXT UNIQUE,
is_active INTEGER DEFAULT 1,
last_login_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) ON DELETE CASCADE
);CREATE TABLE restaurant_tables (
id INTEGER PRIMARY KEY AUTOINCREMENT,
table_number INTEGER UNIQUE NOT NULL,
qr_code TEXT UNIQUE NOT NULL,
capacity INTEGER DEFAULT 4,
location TEXT,
status TEXT DEFAULT 'available',
current_session_id TEXT,
last_occupied_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
icon TEXT,
sort_order INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE subcategories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER NOT NULL,
name TEXT NOT NULL,
icon TEXT,
sort_order INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE
);CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
subcategory_id INTEGER NOT NULL,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL CHECK(price > 0),
image_url TEXT,
emoji_icon TEXT,
is_vegetarian INTEGER DEFAULT 0,
is_spicy INTEGER DEFAULT 0,
is_available INTEGER DEFAULT 1,
preparation_time INTEGER,
calories INTEGER,
allergens TEXT,
customization_options TEXT,
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (subcategory_id) REFERENCES subcategories(id) ON DELETE CASCADE
);CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT UNIQUE NOT NULL,
user_id INTEGER,
table_id INTEGER,
table_number INTEGER,
session_id TEXT,
status TEXT DEFAULT 'pending',
order_type TEXT DEFAULT 'dine_in',
payment_status TEXT DEFAULT 'pending',
payment_method TEXT,
subtotal REAL DEFAULT 0,
tax_amount REAL DEFAULT 0,
discount_amount REAL DEFAULT 0,
total_amount REAL DEFAULT 0,
special_instructions TEXT,
estimated_ready_at DATETIME,
completed_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (table_id) REFERENCES restaurant_tables(id)
);
CREATE TABLE order_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
product_name TEXT NOT NULL,
product_price REAL NOT NULL,
quantity INTEGER NOT NULL CHECK(quantity > 0),
customizations TEXT,
subtotal REAL NOT NULL,
status TEXT DEFAULT 'pending',
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);CREATE TABLE cart_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
user_id INTEGER,
table_id INTEGER,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL CHECK(quantity > 0),
customizations TEXT,
added_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);POST /api/v1/auth/register- User registrationPOST /api/v1/auth/login- User loginPOST /api/v1/auth/logout- User logoutPOST /api/v1/auth/google- Google OAuthPOST /api/v1/auth/facebook- Facebook OAuthGET /api/v1/auth/profile- Get user profilePATCH /api/v1/auth/profile- Update user profilePOST /api/v1/auth/change-password- Change passwordPOST /api/v1/auth/forgot-password- Forgot passwordPOST /api/v1/auth/reset-password- Reset passwordPOST /api/v1/auth/refresh-token- Refresh JWT token
GET /api/v1/menu- Get full menuGET /api/v1/menu/categories- Get categoriesGET /api/v1/menu/categories/:id/subcategories- Get subcategoriesGET /api/v1/menu/products/:subcategoryId- Get products by subcategoryGET /api/v1/menu/product/:id- Get product detailsGET /api/v1/menu/search- Search products
GET /api/v1/cart- Get cartPOST /api/v1/cart- Add item to cartPATCH /api/v1/cart/:id- Update cart itemDELETE /api/v1/cart/:id- Remove cart itemDELETE /api/v1/cart- Clear cart
POST /api/v1/orders- Create orderGET /api/v1/orders- Get user ordersGET /api/v1/orders/:id- Get order detailsPATCH /api/v1/orders/:id/status- Update order statusDELETE /api/v1/orders/:id- Cancel order
GET /api/v1/admin/users- Get all usersGET /api/v1/admin/orders- Get all ordersGET /api/v1/admin/menu- Get menu managementPOST /api/v1/admin/menu- Add menu itemPATCH /api/v1/admin/menu/:id- Update menu itemDELETE /api/v1/admin/menu/:id- Delete menu item
- Cafe URL:
http://subdomain.localhost:4002 - Super Admin URL:
http://localhost:4002
The system identifies the restaurant from the subdomain:
- Extract subdomain from request host
- Look up restaurant in database
- Add
restaurant_idto request context - Filter all data by
restaurant_id
| Role | restaurant_id | Access Level |
|---|---|---|
| Super Admin | null | All cafes, full access |
| Cafe Admin | specific ID | Their cafe only, full access |
| Staff | specific ID | Their cafe only, limited access |
| Customer | specific ID | Their cafe only, customer access |
- User logs in (email/password or Google OAuth)
- Backend validates credentials
- Backend generates JWT access token + refresh token
- Frontend stores tokens in Redux (persisted to localStorage)
- Frontend sends access token in Authorization header
- Backend validates token on protected routes
- Refresh token used to get new access token
- User clicks "Continue with Google"
- Google popup opens
- User authenticates with Google
- Google returns ID token
- Frontend sends ID token to backend
- Backend verifies token with Google
- Backend creates/updates user
- Backend generates JWT tokens
- User logged in
Backend uses middleware to protect routes:
authenticate- Requires valid JWT tokenauthorize- Requires specific rolefilterByTenant- Filters data by restaurant_id
VITE_API_URL=http://localhost:4002/api/v1
VITE_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
VITE_APP_NAME=ArtHaus CafΓ©
VITE_APP_VERSION=1.0.0NODE_ENV=development
PORT=4002
API_VERSION=v1
DB_PATH=./data/arthaus.db
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=12
CORS_ORIGIN=http://localhost:5173
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
LOG_LEVEL=infocd server
node scripts/createRestaurant.js "Cafe Name" subdomain# Cafe Admin
node scripts/createAdmin.js admin@cafe.com password "Admin Name" admin <restaurant_id>
# Super Admin
node scripts/createAdmin.js superadmin@domain.com password "Super Admin" super_adminnpm run dev # Start development server
npm run build # Build for production
npm run preview # Preview production buildnpm run dev # Start development server with nodemon
npm start # Start production server- Build the project:
npm run build - Deploy
dist/folder - Set environment variables in deployment platform
- Update
VITE_API_URLto production backend URL
- Set environment variables
- Deploy Node.js application
- Ensure SQLite database is persisted (use volume)
- Configure CORS for production domain
- Set up SSL/HTTPS
- Change JWT_SECRET to strong random string
- Enable HTTPS
- Configure CORS for production domain
- Set up database backups
- Enable rate limiting
- Configure logging
- Set up monitoring
- Add error tracking (Sentry)
- Configure Google OAuth for production domain
- auth: User authentication state (persisted)
- cart: Shopping cart state (persisted)
- menu: Menu data (not persisted)
- order: Order data (not persisted)
- ui: UI state (not persisted)
- Header: Navigation with user profile
- BottomNav: Mobile navigation
- CartSidebar: Shopping cart
- ProductModal: Product details
- AdminDashboard: Admin panel
- ProfileScreen: User profile
Centralized Axios client with:
- Request/response interceptors
- Token refresh logic
- Error handling
- Session ID management
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
This project is proprietary software. All rights reserved.
For support, contact the development team.