Multi-Tenant Store and Operations Platform
Small retailers need a storefront customers can buy from and a back office showing orders, inventory, and the day's numbers — but they can't afford to run and reconcile two separate systems.
Industry
Small retail (product concept, no client)
Role
Design & full build — self-initiated product concept
Impact
A working concept: a storefront and a back office running from one codebase and one database, with every store's data fully isolated at the database level.
01 - Problem
The problem
Most small retailers I've seen run the same setup: a website builder for the storefront, a spreadsheet for inventory, and someone copy-pasting orders between them at day's end. The two never share data, so stock counts drift, orders get missed, and 'how did we do this month' means an evening of manual reporting. Tools that solve both sides are priced for much bigger operations.
I wanted to test one idea: could a single platform give each store its own storefront and its own operations dashboard for orders, inventory, and reporting, on one codebase, with each store's data strictly isolated? The hard part isn't the screens — it's the data architecture. Multi-tenancy only works if isolation is guaranteed by structure, not by developers remembering to filter. So this was as much a systems exercise as a product one.
02 - System Flow
How the system moves
Shopper places order
Next.js storefront, tenant subdomain
Tenant resolved
Middleware maps subdomain to store
Order written
Supabase Postgres, row-level security
Stock decremented
Postgres function inside transaction
Dashboard updates
Supabase realtime order feed
Reports aggregate
Tenant-scoped SQL views
Owner reviews day
Operations dashboard on Vercel
03 - Build
What I built
Tenant-aware data model with database-level isolation
I started with the schema, not the screens. Every table in Supabase Postgres — products, orders, inventory movements, customers — carries a store ID, and row-level security enforces that a session touches only its own tenant's rows. Stock changes run through a Postgres function inside a transaction, so an order and its inventory decrement succeed or fail together. This is the part I'd defend hardest: isolation has to live in the database, because application code will eventually have a bug.
One storefront codebase, many stores
The customer-facing side is a Next.js app on Vercel, where middleware resolves the subdomain to a tenant and loads that store's catalog, branding, and settings. Adding a store means adding a row and a subdomain, not a deploy. Orders write straight into the same tenant-scoped tables the back office reads — no API integration between two systems, because there's only one system.
Operations dashboard for orders, inventory, and reporting
Each owner gets a back office: a live order feed via Supabase realtime, inventory levels with low-stock flags, and a reporting dashboard on tenant-scoped SQL views for daily sales and top products. It replaces the spreadsheet-and-copy-paste routine with internal tools that read the same data the storefront writes, so the numbers an owner sees are always true.
04 - Impact
Business impact
One codebase serves many stores safely: tenant isolation is enforced at the database with row-level security, not just in app code, so a missed filter returns nothing instead of another store's orders.
Structure before software: the tenant-aware schema was designed and tested with seed data before a single dashboard screen — the same order of operations I push for in client automation work.
Small retailers don't need two stitched-together systems — storefront and operations dashboard share the same Postgres tables, so there's no sync job to break and no end-of-day reconciliation.
Covers the full build path I offer as a consultant: data model, application, Vercel deployment, and documentation for provisioning a new tenant.
05 - Edge Cases
What had to be handled
A query that forgets to filter by store: row-level security scopes every read and write to the authenticated tenant, so the worst case is an empty result, never another store's data.
Two shoppers buying the last unit at once: stock is decremented inside a transaction that checks quantity first, so the second order is rejected with a clear out-of-stock message instead of overselling.
A subdomain that maps to no tenant: middleware falls back to a neutral landing page instead of erroring or serving the wrong store.
A product edited or archived after a sale: each order line snapshots the product name and price at purchase, so reporting and order history stay accurate when the catalog changes.
06 - Improvements
What I would improve next
Self-serve tenant onboarding — provisioning a store is currently a documented script I run by hand; next is a signup flow that creates the tenant, seeds the schema, and assigns the subdomain automatically.
Payments per tenant — the concept stops at order capture; wiring in a payment provider with per-store accounts and payout reporting is the obvious next layer.
Staff roles inside each store — every dashboard user is effectively an owner today; separating owner, manager, and floor-staff permissions would suit stores with more than one person behind the counter.
Related capabilities