The problem
A delivery SaaS that had to serve restaurants, drivers, and end customers without becoming a monolith that ships every Friday at 2am. The owners wanted independent scaling per surface — order traffic spikes at lunch, delivery tracking is steady, payment is bursty around end-of-day reconciliation — and a clean separation between business domains so the team could ship features without stepping on each other.
Architecture
Seven NestJS 11 services, each owning its own data and contract, sharing patterns but never databases:
pedeja-delivery-{auth, user, catalog, order, payment, notification, delivery}-api
───────────────────────────────────────────────────────────────────────────────
auth sessions · refresh · token rotation
user profiles · addresses · roles
catalog restaurants · menus · items · pricing
order placement · lifecycle state machine
payment providers · settlements · refunds
notification email · push · in-app dispatch
delivery rider assignment · ETA · proof of delivery
Each service exposes a versioned REST API documented with Swagger. Inter-service communication is HTTP for synchronous reads and Redis-backed queues for asynchronous side effects (notifications, settlement, ETA updates). Auth is centralized — a single JWT crosses every service, validated per-request.
Web backoffice
React 19 + Vite 7 with TanStack Router (file-based routing, automatic code splitting) and TanStack Query v5 for server state. UI primitives are shadcn/ui over Radix, styled with custom Tailwind v4 tokens. Forms run on React Hook Form + Zod with schemas mirroring the API contracts. Client state lives in Zustand stores (auth, UI, theme).
For local dev I wired MSW mocks against every service so the backoffice can boot and exercise the full UI without any backend running.
Mobile
The customer-facing app is Expo / React Native, sharing the domain types via a generated client so a change to an order schema in the backend propagates type-safely to both surfaces.
Outcome
Each service deploys independently. Adding a new payment method touches one service and zero others. Scaling lunch rush means scaling order and payment only — the rest stays at baseline. The web backoffice ships features at its own cadence, decoupled from the mobile release train.