The Problem: Retail Shops Can't Afford Downtime
In Ghana and across West Africa, internet connectivity is a business risk — not a background concern. Point-of-sale systems that depend on a live cloud connection bring entire shops to a halt the moment connectivity drops. Inventory can't be checked. Sales can't be processed. Staff stand idle.
Off-the-shelf solutions like Shopify POS or Square are either priced out of reach for SME retail, don't support local payment methods, or simply require the kind of broadband reliability that many markets don't have.
Nexus Retail OS was built to solve this from first principles: a multi-tenant, cloud-synced retail management platform where the mobile POS app works fully offline — syncing when connectivity returns, with conflict resolution built in from day one.
Architecture Overview
Nexus is a full monorepo built with Turborepo and pnpm workspaces, containing three interconnected apps sharing common packages:
nexus-retail-os/ # Turborepo monorepo
├── apps/
│ ├── api/ # NestJS + PostgreSQL (multi-tenant API)
│ ├── web/ # Next.js 14 (merchant dashboard)
│ └── mobile/ # React Native + Expo (offline-first POS)
├── packages/
│ ├── ui/ # Shared component library
│ ├── types/ # Shared TypeScript types
│ └── config/ # Shared configurations
└── turbo.json
Multi-Tenant API — NestJS + PostgreSQL
The backend uses schema-per-tenant isolation in PostgreSQL. Each merchant organisation gets a dedicated schema with full data separation, avoiding the performance pitfalls and security risks of row-level multi-tenancy at scale.
- Guards & Interceptors: Tenant context is resolved from the JWT on every request, injected into a request-scoped
TenantService - Migrations: Tenant schemas are provisioned programmatically on registration using
pgraw client before handing off to TypeORM - RBAC: Role-based access control with
Owner,Manager, andCashierroles at the tenant level
Offline-First Mobile POS — React Native + Expo + SQLite
The POS app is the core differentiator. It runs a full local SQLite database — products, inventory, pending transactions, customer data — and processes sales end-to-end without a network connection.
The Sync Protocol:
- All local writes are journaled with a
synced: falseflag and a vector clock timestamp - A background
SyncServicemonitors connectivity via@react-native-community/netinfo - On reconnection, unsynced records are batched and sent to the API sync endpoint
- The API resolves conflicts using last-write-wins with server timestamp authority for inventory, and merge strategy for transaction logs (both sides are preserved as the source of truth for sales history)
// SyncService — simplified conflict resolution logic
async resolveConflict(local: SyncRecord, remote: SyncRecord): Promise<SyncRecord> {
if (local.entityType === 'transaction') {
return this.mergeTransactions(local, remote); // both are valid sales
}
// Inventory: server timestamp wins to prevent double-sells
return local.updatedAt > remote.serverUpdatedAt ? local : remote;
}
Merchant Dashboard — Next.js 14
The web dashboard provides the management layer merchants access from any device:
- Inventory Management: Real-time stock levels with low-stock alerts
- Sales Analytics: Daily/weekly/monthly revenue with export to CSV
- Staff Management: User roles, shift tracking, cashier performance reports
- Multi-Branch: One account, multiple shop locations — each with independent stock
- Subscription Billing: Stripe-integrated plan management with usage metering
Technology Stack
| Layer | Technology |
|---|---|
| API | NestJS, TypeORM, PostgreSQL (schema-per-tenant) |
| Dashboard | Next.js 14, App Router, Tailwind CSS, shadcn/ui |
| Mobile POS | React Native, Expo, SQLite (expo-sqlite), Zustand |
| Monorepo | Turborepo, pnpm workspaces |
| Auth | JWT + Refresh Tokens, per-tenant RBAC |
| Payments | Stripe (subscriptions + usage metering) |
| Infrastructure | Docker, Render (API), Vercel (dashboard) |
| CI/CD | GitHub Actions (lint → test → build → deploy) |
Key Engineering Decisions
Why Schema-Per-Tenant (Not Row-Level)?
Row-level multi-tenancy with a tenant_id column on every table creates invisible risks: one missed WHERE tenant_id = ? clause leaks cross-tenant data. Schema isolation makes that impossible at the database level — and PostgreSQL's connection pooling via PgBouncer in transaction mode keeps connection overhead manageable.
Why React Native + Expo (Not Flutter)?
Flutter was seriously evaluated. React Native won because:
- The API and dashboard are already TypeScript-first — sharing types across the monorepo is seamless
- Expo's managed workflow reduces native build complexity for a solo-built product
- The React Native SQLite ecosystem (
expo-sqlitev14+) now supports real concurrent writes
Why Turborepo?
Running turbo build shaves 60–70% off cold build times by caching task outputs. With three apps sharing common packages, without turborepo each CI run would rebuild shared code from scratch on every push.
Current Status
Nexus Retail OS is in active development. The core API (tenant management, product catalogue, inventory, order processing) and the mobile POS offline-sync engine are feature-complete. The merchant dashboard is in active UI development.
This project is not yet publicly available. Contact me to discuss a pilot or early-access partnership.