A SaaS platform that lets coffee shops and restaurants create a digital menu, get a QR code, and let customers view the menu online by scanning the code — Next.js 16 · NestJS 11 · PostgreSQL · Drizzle ORM.
- ⚡ Instant Digital Menus — Create categories and menu items with photos, prices, and descriptions from a simple dashboard.
- 📱 Public Menu Pages — Mobile-first, server-rendered store pages at
/store/[slug]that look great on any device. - 🧾 Smart QR Codes — Generate and download customizable QR codes (color, size) for every table, or copy the menu link.
- 👁️ View Analytics — Track unique views per device with bot filtering and rate limiting, straight from the dashboard.
- 🖼️ Store Profile — Upload a logo, cover photo, and up to 10 gallery images for your public menu page.
- 🔐 Merchant Auth — JWT-based signup/login (Passport) with protected dashboard routes.
- 🎨 Modern UI — shadcn/ui on Tailwind v4, dark mode, and a warm, conversion-optimized landing page.
| Layer | Tech |
|---|---|
| Frontend | Next.js 16 (App Router, TypeScript) |
| UI | shadcn/ui (Radix + Tailwind v4) |
| Client State | Zustand |
| Server State | TanStack Query |
| Forms | React Hook Form + Zod |
| Backend | NestJS 11 (TypeScript) |
| Database | PostgreSQL |
| ORM | Drizzle ORM |
| Auth | JWT (Passport) |
| Images | Cloudinary |
- Node.js 20+
- npm 10+
- PostgreSQL 15+
git clone <repo-url> menu-manager
cd menu-manager
npm installThis installs dependencies for both frontend/ and backend/ via npm workspaces.
Create backend/.env:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/menu_manager
JWT_SECRET=your-secret-key-change-this
JWT_EXPIRES_IN=7d
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
PORT=4000| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
JWT_SECRET |
Secret used to sign JWT tokens |
JWT_EXPIRES_IN |
Token lifetime (e.g. 7d) |
CLOUDINARY_CLOUD_NAME |
Cloudinary account (image uploads) |
CLOUDINARY_API_KEY |
Cloudinary API key |
CLOUDINARY_API_SECRET |
Cloudinary API secret |
PORT |
Backend port (default 4000) |
Note: Cloudinary is optional for local development — auth and menu CRUD work without it, but image upload will fail until you add credentials.
Create frontend/.env.local:
NEXT_PUBLIC_API_URL=http://localhost:4000/api| Variable | Description |
|---|---|
NEXT_PUBLIC_API_URL |
Backend API base URL |
Then push the database schema (creates merchants, categories, menu_items, and qr_codes tables):
npm run db:pushnpm run dev- Frontend: http://localhost:3000
- Backend: http://localhost:4000
- Health check: http://localhost:4000/api/health
npm run dev # start backend + frontend together
npm run dev:backend # backend only (watch mode)
npm run dev:frontend # frontend only
npm run build # production build for both
npm run db:push # push schema to database
npm run db:generate # generate SQL migration files
npm run db:studio # open Drizzle Studio (GUI DB viewer)Menu-manager/
├── frontend/ # Next.js 16 App Router
│ └── src/
│ ├── app/ # pages (landing, auth, dashboard, store)
│ ├── components/ # shadcn/ui + landing components
│ ├── hooks/ # TanStack Query hooks
│ ├── stores/ # Zustand stores
│ ├── providers/ # React providers
│ └── lib/ # API client, utilities
├── backend/ # NestJS 11 API
│ └── src/
│ ├── auth/ # signup, login, JWT
│ ├── menu/ # categories & menu items CRUD
│ ├── profile/ # store profile, logo, cover, gallery
│ ├── qr/ # QR code generation & download
│ ├── store/ # public menu & view tracking
│ ├── upload/ # Cloudinary image upload
│ └── drizzle/ # schema & DB connection
├── .claude/ # agent configs & plans
└── package.json # root workspace
The monorepo is build-ready with npm run build. The backend (NestJS) and frontend (Next.js) deploy as separate services, both requiring their .env variables above — set DATABASE_URL, JWT_SECRET, and Cloudinary credentials on the backend, and NEXT_PUBLIC_API_URL on the frontend.
MIT
