02 / Multi-tenant SaaS
SmartEatX
White-label ordering and delivery for UK restaurants. One control plane provisions each restaurant its own backend, storefront, branded iOS & Android apps and POS.
- Role
- Lead engineer & architect
- Status
- In production
- Period
- 2026 — now
- Type
- Multi-tenant SaaS
- lines across TS, Go & Elixir
- ~235k
- commits across the backend repos
- 1,100+
- public API SLO · p95 < 300ms
- 99.9%
- test files across the platform
- 232
A / Architecture
Multi-tenant control plane with polyglot services
A super-admin control plane provisions and upgrades isolated tenants. The apps — NestJS business APIs, a Go dispatch engine and Elixir realtime — share a platform library; each ships as one image with api, worker and migrate roles, and every domain event leaves through a transactional outbox.
00 Clients
- Branded apps · Expo
- POS · Expo
- Storefronts · Next.js 16
- Store dashboard
- Super-admin console
01 Edge
- CDN · DNS
- Public API · OpenAPI
- Signed webhooks
02 Services
- Accounts · NestJS
- App API · NestJS
- Support · NestJS
- Delivery engine · Go
- Realtime · Elixir/Phoenix
- Control plane · NestJS
03 Async
- Transactional outbox
- BullMQ
- River jobs · Go
- Event bus
04 Data
- PostgreSQL + PostGIS
- Redis geo
- Double-entry ledger
- Object storage
Before
One backend container per restaurant, a TypeScript delivery engine and Socket.IO realtime.
After
A polyglot monorepo — NestJS business APIs, Go dispatch, Elixir sockets — on a shared, cost-efficient platform with per-tenant isolation and a transactional outbox.
WHY · Dispatch and sockets scale nothing like CRUD APIs, and cost per tenant decides whether a restaurant SaaS works. The new design is sized to grow 100× from launch without another rewrite.
Engineering notes
- 01
Provisioning as a product
Signing up a restaurant creates its database and containers, rolls back cleanly if any step fails half-way and supports side-by-side upgrades — a tenant is a button, not a ticket.
- 02
An app factory
Every restaurant gets branded customer and POS apps built through Expo EAS. The pipeline fingerprints dependencies to catch stale builds, detects the package manager from the lockfile and pulls the real failure out of noisy logs onto a live build dashboard.
- 03
Money that reconciles
A double-entry ledger tagged per client environment underpins remittances, payouts with resync, refunds and promotions — every discount records who actually pays for it.
- 04
The right language per job
The delivery engine moved from TypeScript to Go; realtime moved from Socket.IO to Elixir/Phoenix. Services share one event contract over Redis, designed to grow far beyond launch volume.
- 05
An API documented like Stripe’s
The public delivery API ships with OpenAPI docs, sandbox and production environments, webhooks and RFC 9457 errors — and a test that fails if a documented example ever drifts from the real Go types.
- 06
Auth for many brands
Each restaurant is its own login realm. Admins can stack passkeys, TOTP, email and SMS codes, and money or team changes require step-up authentication.
- 07
Ops without waste
Re-platformed off AWS to cut cost per tenant, with resumable backup and restore; slimmed images by pruning dependencies per bundle; CI tests only the apps a change touches.
B / Infrastructure & delivery
How it ships, scales and stays up.
- Hosting
- Started on AWS with a container per tenant; re-platformed to cut cost per tenant, with guided, resumable backup and restore. Cloudflare at the edge for DNS, media and docs.
- Tenancy
- Each restaurant is provisioned automatically — database, roles and containers — with rollback on partial failure, side-by-side upgrades and image pruning.
- Runtime
- One image per app with api, worker and migrate roles; Compose staging with health checks. Kubernetes with HPA and KEDA on queue depth is the documented next step.
- CI/CD
- Format, lint, typecheck, vulnerability check, unit tests, per-app e2e against real Postgres/PostGIS and Redis, Elixir Credo and tests, OpenAPI drift checks, build to GHCR, migrate-then-roll each app, smoke-test every public host, Slack deploy notes.
- Observability
- Self-hosted Grafana stack — Alloy, Prometheus, Loki, Tempo — with SLO burn-rate panels; OpenTelemetry and slog in Go; alerts to Slack.
- SLOs
- Written SLOs for API availability, latency and live-tracking freshness, with a staged scaling path from replicas to regional deployment.
- Security
- OAuth2 client-credentials tokens between services, scoped by audience; a login realm per brand with passkeys, TOTP, email and SMS codes and step-up auth; KYC for couriers; Renovate for dependencies.
- Contracts
- 269 routes documented with schemas and examples; RFC 9457 errors; a test that fails if documented examples drift from the real types.