A full-stack food ordering application: a customer-facing storefront, a role-based admin panel, and secure Razorpay payment integration. Built as a monorepo with an Express 5 API backend and a React 19 + Vite frontend.
The app is currently single-restaurant in practice β the schema is shaped for multi-tenancy (restaurant_id on most tables), but the frontend has no restaurant-selection UX; public queries always scope to the one active restaurant.
Hosted on free tiers (Vercel + Render) for demo purposes β the backend spins down after inactivity, so the first request after a quiet period can take up to ~50 seconds to wake up.
| Home | Menu |
![]() |
![]() |
| Cart & Bill Summary | Checkout (Razorpay) |
![]() |
![]() |
| Admin Dashboard | Admin Ledger |
![]() |
![]() |
Backend: Node.js, Express 5, PostgreSQL, Redis (optional, ioredis), JWT, Razorpay, Google OAuth, MSG91 (SMS/OTP), Zod, Cloudinary, Pino
Frontend: React 19, Vite, Tailwind CSS 4, GSAP, Three.js, Recharts, Sonner, Axios
Security: Helmet, CORS whitelist, rate limiting, account lockout, HTTP-only cookies, input sanitization, token blacklist, OTP-gated registration
βββ backend/ # Express API server
β βββ src/
β β βββ config/ # Cloudinary, Razorpay, MSG91, DB test config
β β βββ controllers/ # Auth, Cart, Orders, Payments, Profile, Reviews,
β β β # Coupons, Ledger, Restaurant, Upload, Admin*, Google auth
β β βββ db/ # PostgreSQL connection + numbered migrations
β β βββ middleware/ # Auth, adminAuth (role-gated), Validate, Sanitize,
β β β # Rate limit, Upload, Error handler, Request ID
β β βββ routes/ # Customer + Admin API routes
β β βββ scripts/ # Database seed scripts (e.g. seedAdmin)
β β βββ utils/ # Validation schemas, token blacklist, login lockout,
β β # OTP store, coupon utils, Redis client, logger
β βββ src/server.js
βββ frontend/ # React (Vite) client
β βββ src/
β β βββ components/ # Navbar, Footer, Onboarding, OTP input, Star rating,
β β β # animated UI (SplitText, MagicBento, AnimatedList,
β β β # Counter, Aurora background), admin/ layout components
β β βββ context/ # AuthContext, AdminAuthContext, CartContext
β β βββ pages/ # Home, Menu, Cart, Orders, Profile, Contact, Login/Register
β β βββ pages/admin/ # Dashboard, Orders, Menu Items, Categories, Tables,
β β β # Analytics, Reviews, Ledger, Settings
β β βββ services/ # API client, Auth, Cart, Menu, Order services
β βββ index.html
βββ README.md
- Registration gated behind SMS OTP verification (MSG91), plus Google sign-in
- JWT auth via HTTP-only cookies; guided onboarding stepper for new users
- Browse menu by category with search/autocomplete and veg/non-veg filters
- Item detail modal, cart with live totals, coupon codes, and discount display
- Razorpay checkout with signature verification and a success animation
- Order history, live order status, and order cancellation (restores cart)
- Post-delivery item reviews & star ratings
- Profile management and password change
- SMS notification when an admin marks an order "ready" for pickup
- Animated, responsive UI (GSAP, Three.js aurora background, mobile dock nav, desktop pill nav)
- Separate admin authentication (own table, own JWT claim) with role-based access β
owner/manager/staff, enforced per-route - Google sign-in for admin accounts
- Menu item CRUD, featured-item toggle, availability toggle, category management
- Restaurant profile & GST settings management
- Coupon management (owner/manager only)
- Table management
- Order management with full status flow (pending β accepted β preparing β ready β completed); cancelling triggers an automatic Razorpay refund
- Review moderation (view/delete)
- Financial ledger (owner/manager only)
- Analytics dashboard: revenue trends, popular items, payment breakdown, revenue calendar, date-filtered stats
- Image upload via Cloudinary
- Helmet secure headers, CORS whitelist (frontend origin only), HPP prevention
- Rate limiting (auth, OTP, and general API limits) with optional Redis-backed store for multi-instance deployments
- Account lockout after repeated failed logins
- Zod input validation + XSS input sanitization on every request
- HTTP-only JWT cookies, separate
type: "customer" | "admin"claims, real logout via token blacklist - Parameterized SQL queries, bcrypt password hashing, DB transactions for critical operations
- Generic error responses (no user enumeration), request body size limits, structured request logging
- Razorpay webhook signature verification
- Ownership verification on all resource access
All routes are mounted under /api.
POST /register/send-otpβ Validate + send registration OTP (SMS)POST /register/verify-otpβ Verify OTP and create the accountPOST /loginβ LoginPOST /googleβ Google sign-inPOST /logoutβ Logout (auth required)
GET /categoriesβ All categoriesGET /itemsβ All items (filterable by category)GET /featuredβ Featured items for the home page
GET /β Get cart with item detailsPOST /addβ Add item (supports food_type_choice)PUT /updateβ Update quantityDELETE /remove/:itemIdβ Remove itemDELETE /clearβ Clear cart
POST /validateβ Validate a coupon code against the current cart
POST /placeβ Place order from cartGET /β Order history with itemsGET /:idβ Order detailsGET /:id/statusβ Order statusPOST /:id/cancelβ Cancel & restore cart
POST /create-orderβ Create Razorpay orderPOST /verifyβ Verify payment signatureGET /:orderIdβ Payment statusPOST /refundβ Refund payment
GET /β Get profilePUT /β Update name & phonePUT /passwordβ Change passwordGET /ordersβ Order history with payment details
POST /β Create/update a review (auth required)GET /reviewableβ Items the current user can review (auth required)GET /item/:itemIdβ Reviews for an item (public)
POST /razorpayβ Razorpay payment events
POST /auth/login,POST /auth/google,POST /auth/register(owner only),POST /auth/logout,GET /auth/meGET|PUT /restaurant,POST /restaurant(owner),PUT /restaurant/toggle(owner),GET|PUT /restaurant/gstGET|POST|PUT|DELETE /categoriesβ Category CRUDGET|POST|PUT|DELETE /menu-items,PUT /menu-items/:id/featured,PUT /menu-items/:id/availabilityGET|POST|PUT|DELETE /tablesβ Table managementGET|PUT /ordersβ View & update order statusGET /analyticsβ Analytics dataGET /ledger,GET /ledger/summaryβ Financial ledger (owner/manager only)GET /reviews,DELETE /reviews/:idβ Review moderationGET|POST|PUT|DELETE /couponsβ Coupon management (owner/manager only)POST /uploadβ Image upload (Cloudinary)
cd backend
npm install
# Copy .env.example to .env and fill in your values
npm run devcd frontend
npm install
# Copy .env.example to .env and fill in your values
npm run devMigrations are plain numbered .sql files with no migration-runner β apply manually in order:
psql -U postgres -d akio_db -f backend/src/db/migrations/001_create_users.sql
# ... through the highest-numbered file in backend/src/db/migrations/All migrations use CREATE TABLE IF NOT EXISTS / ADD COLUMN IF NOT EXISTS, so they're safe to re-run.
cd backend
node src/scripts/seedAdmin.jsCreates the first admin user from ADMIN_EMAIL / ADMIN_PASSWORD in backend/.env.
See backend/.env.example and frontend/.env.example for the full list with descriptions. Required:
- Database credentials (
DB_*) JWT_SECRET- Razorpay keys (
RAZORPAY_*) - Cloudinary keys (
CLOUDINARY_*) FRONTEND_URL(backend) /VITE_API_URL(frontend)
Optional (each degrades gracefully when unset):
REDIS_URLβ shared rate-limit/lockout/token-blacklist state across multiple backend instances; falls back to in-memory otherwiseGOOGLE_CLIENT_ID/VITE_GOOGLE_CLIENT_IDβ enables Google sign-in for customers and adminsMSG91_*β sends real OTP/order-ready SMS; without it, OTPs are logged server-side and returned asdev_otpfor local dev
Both servers must run simultaneously (separate terminals): backend on PORT from backend/.env (default 5000), frontend via Vite (default 5173). Make sure frontend/.env's VITE_API_URL points at wherever the backend is actually running β Vite bakes this in at dev-server startup, so changing it requires restarting the Vite process.
- Customer side (Backend + Frontend): Nikunj
- Admin side (Backend + Frontend): Bhavya





