Platform

Architecture overview

Totlob has three TypeScript codebases: one Postgres-backed API and two React single-page apps. A family of versioned contract packages binds them together and defines every endpoint's path and schema.


System map

 ┌──────────────────────┐   ┌──────────────────────┐   ┌──────────────────────┐
 │ merchant-dashboard-v2│   │      totlob-pos      │   │ storefronts / tools  │
 │ React 19 · Vite      │   │ React 19 · Vite      │   │ (outside these repos)│
 │ Vercel               │   │ Vercel               │   │                      │
 └──────────┬───────────┘   └──────────┬───────────┘   └──────────┬───────────┘
            │ @totlob/contracts-merchant│ @totlob/contracts-pos   │ contracts-storefront
            │ @totlob/contracts-accounting                        │ contracts-internal
            ▼                          ▼                          ▼
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │ api-v2  ("cypher")  Express 4 + ts-rest · TypeScript · Heroku (cypher-prod)  │
 │  /merchant/*   /pos/v1/*   /storefront/v1/*   /internal/*   /internal/ops/*  │
 │  34 domain modules · PgTyped SQL · bigint money · per-shop ledger            │
 └───────┬───────────────┬───────────────┬───────────────┬──────────────────────┘
         ▼               ▼               ▼               ▼
   PostgreSQL 16     Redis (cache)   RabbitMQ (events)  Cloudinary · Mailgun ·
   (Stackhero)       optional        optional           Google Maps · Algolia ·
                                                        Apple/Google IAP · FX feed

The three codebases

RepoRoleStackHosting
api-v2 (GitHub totlob/cypher)All data and business rules. Serves five audiences: merchant, pos, storefront, internal, customer (empty).Node ≥ 20, Express 4, ts-rest, zod 3, PgTyped, dbmate, vitestHeroku cypher-prod; Postgres on Stackhero
merchant-dashboard-v2Back office for owners and staffReact 19, Vite 6, React Router 7, TanStack Query 5 + Table 9, shadcn/ui, Tailwind 4, RechartsVercel (SPA rewrite)
totlob-posPoint-of-sale tillReact 19, Vite 8, Zustand 5, TanStack Query 5, shadcn/ui, Tailwind 4Vercel (SPA rewrite)

Cross-cutting design decisions

Contracts are the API. Every endpoint is declared in a published @totlob/contracts-* package (ts-rest + zod). The API lifts routes by reference into its routers, and the apps build typed clients from the same objects, so paths and schemas can't drift. See Shared contracts.

RPC over POST. Every route is POST /<audience>/<service>/v1/<kebab-name> with a JSON body. Shop-scoped routes carry shopId in the body. The middleware verifies it and handlers read verifiedShopId(req), never the raw body.

Passwordless auth with rotating refresh tokens. Email + 6-digit OTP. Short-lived HS256 JWT access tokens, plus opaque refresh tokens that rotate on every use and have reuse detection. Both apps serialise refreshes to avoid tripping reuse detection. Apps hand sessions to each other with 60-second single-use handoff codes. See Auth, roles & permissions.

Owner or staff, scoped three ways. A request resolves to a ShopAccess: either owner (everything), or staff with assignments that combine permission codes (what), branches (where) and catalogue category roots (which).

Money is never a float. All three codebases use fixed-point bigint at scale 6, matching Postgres numeric(20,6). The POS contract sends money as strings. The merchant contract uses numbers.

Catalogue prices are stored in USD and converted to the shop's currency on every read, using dated exchange rates (shop override > shared rate).

The server prices everything. The till mirrors the pricing maths for display only. Tax, discounts and totals are decided by the API at create-order, and every order write is idempotent on a client-generated key.

Module ownership. Each table belongs to exactly one API module. Other modules go through its service layer, which keeps per-namespace cache invalidation correct. See Structure & conventions.

Where things run

ConcernWhere
API deployPush to GitHub master triggers the Heroku deploy. release: runs dbmate migrations before new dynos take traffic.
App deploysVercel, per repo, from master.
Background workworker dyno (npm run consumer) for ledger postings and search reindexing, or in-process with WEB_CONSUMES_QUEUE=true (never both).
Scheduled jobsHeroku Scheduler: npm run sync-rates daily at 06:00 UTC (FX, in progress).
LogsBetterStack (Logtail), request metadata only.

master is production

Pushing to master in api-v2 or totlob-pos ships to real users, and API migrations ride along. See Deployment.