System overview
Source:
g2labs-in/billuu-docs@7780d43·content/architecture/system-overview.md· updated 2026-09-26
What Billuu is
Billuu is a multi-tenant restaurant POS by BrewLabs. One backend deployment serves every customer. Each customer’s business data sits in its own PostgreSQL schema inside one shared database, so onboarding a customer needs no new server. The rules that make that safe are on the Multi-tenancy page.
Production is one Azure virtual machine for the API, Netlify for the browser apps, Supabase for PostgreSQL, and Cloudflare in front of everything.
Applications
Every deployable in the workspace, with the stack its own package.json declares.
| Application | Code | Stack | Runs on | Job |
|---|---|---|---|---|
| Backend API | brewlabs-billu-backend | Node.js 20, Express 4, Sequelize 6, Socket.IO, BullMQ, ioredis | Azure VM billuu-app as the Docker Swarm service billuu-backend-poxd1a, port 8000 | Serves every /api/* request |
| Staff POS and back office | brewlabs-billu-frontend | React 18, Vite 6, Tailwind, Redux Toolkit, TanStack Query, socket.io-client | Netlify, and a Cloudflare Worker named billuu-pos reached at billing.billuu.com | Orders, tables, kitchen view, menu, inventory, reports |
| Super admin panel | breww-admin, repo g2labs-in/brewlabs-billu-admin | React 19, Vite 6, Tailwind, Redux Toolkit, MUI, recharts | Cloudflare Worker named billuu-admin, live at superadmin.billuu.com behind a Cloudflare Access email gate | Tenants, subscriptions, plans, platform administrators, and the platform screens behind /dashboard. Most of those screens have no backend and say so |
| Marketing site | billuu_landingPage | Next.js 14, Tailwind, three.js | Not in the current production inventory | Public landing page |
| CRM service | brewlabs-billu-crm | Express 4, Sequelize 6, node-telegram-bot-api, node-cron | Not in the current production inventory | Customer, segment, and campaign records, plus a Telegram bot |
| Docs site | billuu-docs | Astro Starlight, Pagefind, Bun build | No DNS record in the infra repo. site is set to https://doc.billuu.com in billuu-docs/astro/astro.config.mjs | This documentation |
Three of these have no production entry in the infra inventory, so do not assume they are running. brewlabs-billu-crm has no Dockerfile and no entry in /home/codejack/code/linode/CONTEXT.md. The landing page repo ships billuu_landingPage/netlify.toml with the Next.js Netlify plugin, while the infra notes call the site undeployed and name Cloudflare Pages as the intended home. billuu-docs has no hostname in the Cloudflare record set.
The frontend repo carries stale hostnames. brewlabs-billu-frontend/vercel.json rewrites /api/:path* to https://pos-backend-6afs.onrender.com, and brewlabs-billu-frontend/.env.example sets VITE_API_BASE_URL to https://billu.3dpnow.com/api. Neither is the production API hostname today. The infra repo is authoritative for hostnames.
The super admin panel is the one application whose live URL does not serve its own repo. The domain answers with a build of commit 4611679 from the branch testing-phase-with-auth-21-feb-2026, and the panel branch has an unrelated history. The Super admin panel section of this site covers that, and the API gap behind it.
Where production runs
The resource group is billuu-prod. The VM billuu-app is a Standard_B2ls_v2 with 4 GB of RAM, a 64 GB disk, and the static IP 20.235.64.99, in the centralindia region.
| Hostname | Serves | Proxied by Cloudflare |
|---|---|---|
testing.billuu.com | The backend API through Traefik | yes |
dokploy.billuu.com | The Dokploy panel, behind Cloudflare Access | yes |
billuu.com, www | Netlify | no |
billing.billuu.com | Netlify | no |
order.billuu.com | Netlify, the customer ordering app that QR codes point at | no |
main.billuu.com | The VM, unused, kept for parity | no |
The backend container is built from the stabilize-phase-1-2 branch and listens on port 8000. Redis 8 runs on the same host as billuu-redis-engwdt with no published port. Dokploy owns the Traefik dynamic configuration under /etc/dokploy/traefik/dynamic/, so never hand-edit those files.
Port 443 on the VM answers Cloudflare edge addresses and Tailscale and drops everything else. The filter is enforced by /usr/local/bin/billuu-firewall.sh through the DOCKER-USER chain. Port 80 stays open to the world because Let’s Encrypt validates over HTTP from its own addresses.
Request path
flowchart TD Browser["Browser"] Edge["Cloudflare edge"] Netlify["Netlify sites"] Traefik["Traefik on billuu-app"] Backend["Backend container on port 8000"] Redis["Redis 8 on the same host"] Pooler["Supabase pooler in Mumbai"] Postgres["PostgreSQL, public and tenant schemas"]
Browser --> Edge Edge -->|"DNS only hostnames"| Netlify Edge -->|"proxied hostnames"| Traefik Traefik --> Backend Backend --> Redis Backend --> Pooler Pooler --> PostgresThe path in words, for an API call:
- The browser reaches the Cloudflare edge, which holds DNS, TLS, the WAF, and a rate limit of 40 requests per 10 seconds per IP on
/api/auth. - Cloudflare forwards to the VM. Traefik terminates TLS with a Let’s Encrypt certificate and routes to the container on port 8000.
brewlabs-billu-backend/app.jsruns the global middleware: dynamic CORS,helmet,compression, JSON parsing with a 10 MB limit,standardResponseMiddleware,profilerMiddleware, and a 60 second request timeout fromREQUEST_TIMEOUT_MS.applyMultiTenantMiddlewareChains(app)insrc/architecture/databaseMiddlewareChain.jsmounts each route group with its own chain. Tenant routes getUnifiedAuthMiddleware.authenticate, thenUnifiedTenantMiddleware.setupTenantContext, thenmodelsSafetyLayer. Public routes for auth, onboarding, QR, sessions, and the customer app get the shorterpublicChain.- The handler calls
req.dbContext.executeInTenantorreq.dbContext.readInTenant, whichsetupTenantContextinstalls on the request. src/core/database/databaseExecutor.jspicks the schema, wraps the call in a transaction for writes, and hands the handler a set of models bound to that schema. Sequelize writes schema-qualified table names, so the SQL names the schema explicitly.- PostgreSQL answers through the Supabase pooler. Reads without a transaction go through
readWithTenantand never open one.
A GET request on a tenant route is downgraded to readInTenant automatically unless the caller passes forceTransaction.
How the pieces talk
| From | To | Mechanism |
|---|---|---|
| Browser | Backend | HTTPS to VITE_API_BASE_URL, with Authorization: Bearer <jwt> and the headers x-panel-type, x-outlet-id, and x-organization-id set in brewlabs-billu-frontend/src/https/axiosWrapper.js |
| Browser | Backend | Socket.IO over the same HTTP server for order and alert events, set up in src/infrastructure/websocket/socketService.js |
| Backend | PostgreSQL | One Sequelize instance for every schema, created in src/config/unified_database.js |
| Backend | Redis | Cache, rate limiting, and BullMQ queues through ioredis, configured in src/config/redis.js |
| Backend | Razorpay | Payment orders and webhooks in src/controllers/tenant/paymentController.js |
| Backend | Cloudinary | Image uploads through src/utils/imageUpload.js |
| Backend | Firebase | Push notification delivery through the Admin SDK, configured in src/config/firebase.js |
| Backend | SMTP | OTP email through nodemailer in src/auth/otp.service.js |
| Backend | S3-compatible storage | E-bill uploads in src/services/ebillService.js |
| Host | Grafana Cloud | Alloy ships host metrics and container logs, config at monitoring/alloy/config.alloy |
| Host | healthchecks.io | Backup and heartbeat jobs ping a dead-man check |
| UptimeRobot | Public URLs | Four external HTTP checks every five minutes |
The infra notes list Auth0 as part of backend auth. The code does not bear that out. Authentication is a JWT signed in src/core/auth/tokenService.js from process.env.JWT_SECRET and read back by src/core/auth/unifiedAuthMiddleware.js. Google sign-in comes from passport-google-oauth20, and the Firebase Admin SDK is used for push, not for login. Auth0 appears only in the frontend, as @auth0/auth0-react.
Background work
Four things run without a request.
| Job | Trigger | Code |
|---|---|---|
| System metrics refresh | Every 15 minutes, plus once 30 seconds after boot | cron.schedule in brewlabs-billu-backend/app.js, running scripts/update_system_metrics.js |
| Expire unpaid orders | Every 5 minutes | cron.schedule in brewlabs-billu-backend/app.js, which scans TenantRegistry for active tenants and updates Order and Payment |
| FEFO expiry alerts | Daily | startFefoExpiryAlertCron in src/workers/fefoExpiryAlertCron.js |
| Notification, onboarding, and snapshot queues | BullMQ | src/workers/notificationWorker.js, src/workers/onboardingWorker.js, src/workers/snapshotWorker.js, started only when Redis is real |
Startup is not a no-op. brewlabs-billu-backend/app.js runs bootstrapControlPlane, dataFirstInitializer, runStartupMigrations, runStartupValidation, and a schema heal before it listens. Set SKIP_STARTUP_MIGRATIONS=true to skip the last three.
Where to look next
- Multi-tenancy for how one process serves every customer.
- The Platform section of this site for deploys, rollback, and runbooks.
/home/codejack/code/linode/CONTEXT.mdfor the full host, DNS, firewall, and backup inventory. It is the single source of truth for infrastructure.
Sources
/home/codejack/code/linode/CONTEXT.md/home/codejack/code/linode/README.mdbrewlabs-billu-backend/app.jsbrewlabs-billu-backend/package.jsonbrewlabs-billu-backend/Dockerfilebrewlabs-billu-backend/src/architecture/databaseMiddlewareChain.jsbrewlabs-billu-backend/src/config/corsConfig.jsbrewlabs-billu-backend/src/config/unified_database.jsbrewlabs-billu-backend/src/config/control_plane_db.jsbrewlabs-billu-backend/src/config/redis.jsbrewlabs-billu-backend/src/config/firebase.jsbrewlabs-billu-backend/src/config/config.jsbrewlabs-billu-backend/src/core/database/databaseExecutor.jsbrewlabs-billu-backend/src/core/tenant/unifiedTenantMiddleware.jsbrewlabs-billu-backend/src/core/auth/tokenService.jsbrewlabs-billu-backend/src/core/auth/unifiedAuthMiddleware.jsbrewlabs-billu-backend/src/infrastructure/websocket/socketService.jsbrewlabs-billu-frontend/package.jsonbrewlabs-billu-frontend/index.htmlbrewlabs-billu-frontend/vite.config.jsbrewlabs-billu-frontend/vercel.jsonbrewlabs-billu-frontend/.env.examplebrewlabs-billu-frontend/src/App.jsxbrewlabs-billu-frontend/src/https/axiosWrapper.jsbrewlabs-billu-crm/package.jsonbrewlabs-billu-crm/src/index.jsbilluu_landingPage/package.jsonbilluu_landingPage/netlify.tomlbilluu-docs/docs.manifest.yamlbilluu-docs/astro/astro.config.mjs