How it's built · Technical blueprint
The engine behind every roll.
Maki Taki isn't getting a brochure website. It's getting a direct-to-customer growth platform: branch-aware ordering, loyalty, gamification, referrals, promotions, CRM and a full admin portal, all running on one clean, auditable backend that your team controls without code deployments.
Architecture principle
The growth loop
Every feature earns its place in the loop.
The product revolves around one cycle: Discover → Order → Earn → Engage → Reward → Return → Refer. Each stage is driven by specific features and moves a measurable number. Nothing is built just because it looks nice.
System architecture
One backend. Every channel.
The website, the admin portal and any future app or POS all talk to the same versioned API. Business rules live in one trusted place. Slow work runs in background workers, and every outside provider sits behind a swappable adapter.
Each part has one job. The arrows show who talks to whom. Solid arrows are live requests, and dashed arrows are optional or future paths.
← Swipe the diagram sideways to see all of it →
Branch and fulfilment model
Branch-aware by design
Every order belongs to exactly one fulfilment branch. Prices, stock, fees, minimum order, delivery eligibility and promotions are all evaluated against that branch before payment.
Tech stack
Proven tools, chosen for the long run.
Mainstream, well-supported technology that any competent team can maintain. No exotic bets and no lock-in to a single vendor. Here's what each piece is and why it matters to the business.
Domain modules & code layout
24 focused modules, one tidy monorepo.
The backend is split into small domain modules. Each owns its rules, data and tests. Web, admin, API and workers live in one repository and share types, so a change to an order or a promotion stays consistent everywhere.
maki-taki/ ├─ apps/ │ ├─ web/ # Customer website / PWA │ ├─ admin/ # Admin portal │ ├─ api/ # Backend API │ └─ worker/ # Queues, campaigns, scheduled jobs ├─ packages/ │ ├─ ui/ # Shared UI / design system │ ├─ contracts/ # DTOs, schemas, generated API types │ ├─ domain/ # Shared domain rules │ ├─ config/ # Lint / TS / config │ └─ testing/ # Fixtures and test helpers ├─ infra/ # Docker / IaC / deployment └─ docs/ # Architecture, ADRs, API docs
Data model
A data model that remembers everything.
One customer record connects orders, points, rewards, games and referrals. Orders keep a frozen snapshot of what was bought. Points live in a ledger, not a single editable number. Hover any entity to see its key fields.
← Swipe the diagram sideways to see all of it →
An OrderItem stores the product, price and modifiers as they were at the time of sale. Editing the menu tomorrow never rewrites yesterday's orders.
Points are the sum of append-only LoyaltyLedger rows. The balance on LoyaltyAccount is only a cache and can always be rebuilt.
Business records are soft-deleted or archived. Historical orders, ledgers and the AuditLog are immutable.
How the critical flows work
Where money and rewards move, the server decides.
These four flows carry the commercial risk: orders, pricing, points and prizes. Step through each one to see how it stays correct even with double taps, retried payments or someone tampering with the browser.
Order lifecycle
Every order moves through explicit states. Every change is timestamped and appended to an immutable status history, so you can always answer "what happened, when, and who did it".
Checkout quote
The browser sends only a cart and the customer's choices. The server recalculates branch, prices, fees, promotions and points. Placing the order carries an idempotency key, so a double tap or a network retry can't create two orders.
The loyalty ledger
Points are never stored as one editable number. Every change is a new ledger row with its amount, reason, source and expiry. The balance is derived from those rows, which makes points auditable, reversible and impossible to spend twice.
| # | When | Type | Points | Source | Expiry |
|---|
cached on LoyaltyAccount · rebuildable
Lucky Roll
A qualifying action grants a play token. Playing consumes it atomically, then the server picks a prize from a weighted pool, enforces stock and caps, and writes an audit record. The browser only plays the animation.
Browser
1 token available
Server · gamification
Audit · GamePlay record
Promotion engine
Promotions are rules, not price edits.
Marketing builds offers from ten rule areas, with no developer and no hard-coded discounts. Every rule is stored as validated, versioned data, and the same engine checks it in the cart and again at checkout, so margins stay protected.
{ "conditions": { "all": [ {"field":"branch_id","op":"in","value":["BR01","BR02"]}, {"field":"cart.subtotal","op":"gte","value":25}, {"field":"customer.orders_90d","op":"gte","value":1} ] }, "benefit": {"type":"free_product","product_id":"EDAMAME01","qty":1}, "limits": {"per_customer":1,"total":500}, "stacking": {"mode":"exclusive"} }
Safety: the API validates the allowed fields and operators and evaluates them in a controlled engine. It never runs arbitrary code from the database.
Margin protection
Guardrails built in
Referrals ride the same rails
Rewarded only for real orders
Attribution survives landing → registration → first qualifying order. The referrer's reward is issued only after the friend's first qualifying paid or delivered order, with self-referral and duplicate-account controls.
API surface
A clean, versioned front door.
Versioned REST endpoints under /api/v1, documented with OpenAPI. The website uses them today. A mobile app, POS, delivery system or WhatsApp bot can use the very same ones tomorrow. GraphQL is optional and not needed for the MVP.
API requirements
Security, privacy & reliability
Safe with money, data and trust.
These rules are enforced in the backend and covered by automated tests, including concurrency tests for the last coupon, points double-spend, prize inventory and duplicate payment webhooks.
Analytics & success framework
Judged by behaviour, not feature count.
Analytics are first-class events, not just database reports. Every meaningful action is recorded with its source, so we can prove which promotions, games and campaigns actually bring orders. We set a pre-launch baseline and compare cohorts after launch.
Event taxonomy · minimum events
Core KPIs
Primary business outcomes
Guardrails
Attribution
Source, medium, campaign, content, referral code and landing page are kept in an attribution session. Orders link to the last-touch campaign by default, and raw events are kept for other models later.
The closed loop
Customer behaviour generates data
Segmentation selects relevant audiences
Controlled promotions & rewards create engagement
Orders produce measurable revenue
Analytics show what to continue, stop or improve
MVP scope
Commercially usable, not a prototype.
The first release already takes real orders, runs loyalty and promotions, and reports results. The more advanced versions of each capability come later, once real data shows where they pay off.
Non-goals for MVP
- A full accounting / ERP system
- Replacing a sophisticated restaurant POS
- An independent delivery-driver marketplace
- Native iOS / Android apps (the web app installs as a PWA)
How we'll know the MVP is done · acceptance criteria
Delivery roadmap
Six phases, each with a clear outcome.
We build the safe foundation first, then direct ordering, then the growth engine on top. Each phase ends in something the business can use and measure.
Durations are set together once the open decisions below are answered.
Open decisions · What we need from you
Twelve answers before production.
These are business decisions, not technical ones, so they're yours to make. Tick them off as we agree on them. Each answer is recorded as configuration or an architecture decision record.
Ready to roll?
See the platform from both sides: the ordering experience your customers will love, and the portal your team will run it from.