We built CafePro as a single Next.js 15 application on MongoDB where one installation serves many businesses, and tied the menu, the till and the books together so cost and margin are always current.
Key design decisions
- Shared database, enforced tenancy. 38 of the 52 Mongoose models are tenant-scoped. A schema plugin throws on any query without a
tenantId filter; cross-tenant access needs an explicit opt-in on the query.
- Cost that follows the price list. Ingredients are priced per pack and recipes carry portions and a waste allowance, so a price change re-costs every recipe and every menu item’s margin. Each POS and invoice line snapshots its price and recipe cost, so historic reports never move.
- Two COGS bases, one P&L. The profit-and-loss report uses recipe COGS (the cost of what was sold) by default or purchases as an alternative, and shows the variance between them.
- Customer-designed roles. Permissions are
module.action pairs (26 modules, 6 actions) held by role documents and resolved on every request, with monetary limits for expense approval and discounts. Every server action goes through a guarded wrapper (session → tenant → permission → validation → audit), and a repo script fails the check if one does not.
- Integrity where it matters. Money is stored in integer minor units of each business’s currency; payments, loyalty and refunds run in MongoDB transactions; business days and shifts are kept in the business’s time zone.
- Reliable mail. Every email goes through a database-backed queue with backoff retries and a delivery log, over per-business SMTP or Gmail.
- AI as an assistant only. Imports land in a review grid and commit all-or-nothing with batch rollback; assistants return drafts, the app computes every number, and each business can switch AI off.
Architecture
React Server Components render the back office, the Super Admin console and the public websites (the platform site and one site per business at /cafe/<slug>). Server Actions and route handlers call domain services in lib/services, which use Mongoose on a MongoDB replica set. Two protected cron endpoints deliver the mail queue and run daily reminders, expiry alerts and overdue-invoice notices. Files go to Cloudinary or local private storage served through permission-checked routes, and the landing pages use three.js, GSAP and Lenis for the animated 3D hero.