A retail-chain operator wanted to productise their internal inventory tooling and resell it to peer chains. We rebuilt it as a multi-tenant SaaS platform — and shipped to 60+ retail stores across four customer chains in seven months.
The client had 22 stores running on a custom inventory app the founding team had built five years ago in PHP. It worked — barely — for them; it would never sell to anyone else. Two peer chains had asked to license it. The brief: turn the in-house tool into a multi-tenant SaaS that could run any retail chain, not just this one.
We rebuilt the platform on a multi-tenant foundation: org_id everywhere, configurable hierarchies, plug-able pricing rules, and a Flutter scanner app for floor-staff. New chains onboard in 3-5 days now instead of 6 weeks.
Org-scoped login, store-hierarchy builder (org → region → cluster → store), category tree editor, master SKU catalog, GRN, transfer, sale, return, stock-take. Each chain sees only their data; the spine is shared.
Barcode scan via phone camera (no dedicated hardware needed), GRN flow, transfer-in/out flow, stock-take floor mode, partial-quantity entry, offline-tolerant. Works on a Rs 9,000 phone.
Live store-level inventory, replenishment recommendations (we ship a baseline rule + each customer can override), GST report, sales report, ageing-stock dashboard, SLA dashboard for slow-mover liquidation.
Pricing rules (MRP / sale / VIP / regional / store-override), GST mapping, discount stacking, expiry handling — all data, all editable in the UI. Chain B's '40% off Friday' rule is a config, not a code change.
NestJS multi-tenant API with row-level org_id enforcement, Postgres with per-org connection pools, Redis pub/sub for real-time inventory updates across stores, BullMQ for async (e.g. nightly aging-stock recompute), AWS Mumbai.
We picked the stack for fit, performance under realistic load, and operational simplicity. Here is the breakdown:
NestJS multi-tenant guard at the API edge: every request resolves to an org from the JWT, every Postgres query is scoped to that org via a guard. We tested this with adversarial fuzz tests; in 4M synthetic cross-tenant requests, zero leaks.
Postgres row-level security plus app-level guards: belt-and-braces. RLS catches the 0.001% case where a developer forgets the WHERE org_id; the app guard catches the 99.999% normal flow with better performance.
Flutter scanner over native: any cheap Android phone is now a scanner. Customers don't have to buy Honeywells; staff use their own phones (with a small allowance). Onboarding hardware cost dropped from Rs 8,000/store to zero.
Real-time inventory sync at 60-store scale.
The customer's biggest pain was: store A has stock, store B has a customer asking for it, the system says no — sale lost. Real-time inventory across 60 stores sounds simple until you actually try.
We modeled inventory as a stream of immutable events (GRN, sale, transfer-in, transfer-out, adjustment). Aggregations are computed as projections; reads from cached aggregates with sub-second freshness. Writes go to Postgres; Redis pub/sub fans out to the affected stores' UIs.
Cross-store transfer time dropped from overnight to sub-minute. Stockout rate fell 45%. Saturday-evening rush — the toughest test — went from 'frequent system lag' to imperceptible.
We dogfooded with the founding chain — all 22 stores live first. Three months of running both old and new in parallel before cut-over. Caught seven data-quality bugs in legacy data; would have been silent failures otherwise.
External customer #1 went live in week 24. The on-boarding playbook was: org-create, hierarchy-import, SKU-import, training, parallel-run for two weeks, cut-over. Week 28: customer #2. Week 31: customers #3 and #4 in parallel.
“We turned a five-year-old internal tool into a SaaS we can sell. The multi-tenant rebuild was the hard part — and ITD got the security layer right, which is what matters when you sell to other retailers.”
Founder & CEO
Retail Chain · Founding Customer
Decide multi-tenant strategy before line one of code. We started with row-level org_id and pivoted to row-level + RLS in month four. Doable, but cost us 3 weeks of refactoring. Decide RLS-on or RLS-off in week one and stick to it.
Going from internal tool to sellable SaaS is a different engineering problem than building either alone. Talk to a team that has shipped both.
Get a Free Consultation