Inspiration

Running a retail store in northern Nigeria is a full-time act of improvisation.

I grew up watching store owners in Yola manage inventory the same way it was done 30 years ago, handwritten ledgers, loose receipts stuffed into drawers, and stock counts that only happened when something went visibly wrong. When goods expired, the loss was absorbed silently. When stock ran out mid-trading day, the customer left. When a supplier claimed they had delivered what the store denied receiving, there was no record to settle it.

The software that exists for this problem is built for other markets. It assumes stable internet, credit card payments, technical staff, and a business environment that most small Nigerian retailers simply don't operate in. What these store owners need is not a scaled-down enterprise tool, they need something built specifically for how they actually work: cash-dominant, paper-based supplier relationships, consignment arrangements, goods in Hausa and English, and a tablet shared by three different cashiers on rotating shifts.

Trova is the system these stores should have had from day one.


What It Does

Trova is a full-stack B2B inventory management system and point-of-sale built for small to mid-size retail stores. It replaces the old notebook method with a real system, one that tracks every delivery, every sale, and every naira, in real time.

Stock Intake with Batch Tracking Every delivery is logged as a batch not just "we received Milo." The record captures which vendor supplied it, whether it is a direct purchase or consignment arrangement, quantity received (by unit or by carton/pack), total purchase cost, auto-calculated cost per unit, gross margin, and expiry date. This is the foundation everything else is built on.

FEFO Point-of-Sale The POS screen processes sales using First-Expiry-First-Out logic. When a cashier completes a sale, Trova automatically queries all batches for each product ordered by expiry date ascending and deducts from the oldest stock first, all inside a single Aurora PostgreSQL transaction. No expired goods quietly sitting behind fresh stock. No partial deductions that leave the database in an inconsistent state.

Proactive Alerts Low stock and expiry warnings surface before they become losses, not after. Items expiring within 7 days appear in red on the dedicated alerts page. The dashboard shows the total alert count the moment any user logs in, regardless of role.

PDF Receipts Every completed sale generates a downloadable PDF receipt with store name and address, itemised product list, payment method, amount received, and change given. Generated client-side with @react-pdf/renderer, no server round-trip needed.

Owner-Level Analytics Revenue trends, top-selling products ranked by units and margin, daily sales bar charts, and vendor spend summaries, all filterable by any custom date range and exportable as CSV.

Multi-Currency Support Stores configure their trading currency at setup (NGN, USD, GBP, EUR, and more). Every price, cost, total, margin, and receipt throughout the app formats correctly for that currency using locale-aware formatting. The same codebase serves a store in Lagos and a store in London.

Consignment Vendor Tracking Vendors marked as consignment are treated differently throughout the system. Their supplied stock is tracked separately with outstanding units and estimated value owed displayed per vendor, so when the consignment vendor comes to collect, the store owner has the numbers.

Role-Based Team Management Owners invite staff via secure token-based join links. Staff can process sales and log intake but cannot access financials, analytics, or store settings. Every action is recorded under the acting user's name.


How i Built It

Framework: Next.js 16 App Router with React Server Components and server actions for all data mutations. The App Router's layout system enables the multi-tenant dashboard shell, sidebar, topbar, and role-checked middleware without duplicating auth logic across routes.

Database — Amazon Aurora PostgreSQL:

Aurora PostgreSQL is the core infrastructure decision that makes everything else possible.

The database is provisioned through the Vercel Aurora Marketplace integration, which injects all connection credentials as environment variables automatically, no manual credential management after the initial setup. The connection pool uses IAM token authentication via AWS RDS Signer rather than static username/password credentials. The token is rotated automatically at the connection level, meaning Trova uses the same auth model that enterprise production systems use, from day one.

The schema covers 8 domain tables designed specifically around Trova's requirements:

Table Purpose
stores Multi-tenant root. Each store is a completely isolated unit.
users Users are scoped to exactly one store, with a role (owner or staff).
categories Product groupings, scoped per store.
vendors Supplier records with type (direct or consignment).
products SKUs with selling price, cost, unit, reorder level, and current stock quantity.
batches Stock intake records: vendor, qty_received, qty_remaining, cost_per_unit, expiry_date, is_consignment, reference_number.
sales Transaction headers with receipt number, total, payment method, and cashier.
sale_items Line items linking each sale to specific batches, recording qty_sold and unit_price at time of sale.

Why Aurora specifically and not a simpler option: The FEFO batch deduction requires an atomic database transaction to query available batches ordered by expiry date, deduct quantities across multiple batch rows, insert sale_items records, all in a single operation that either fully succeeds or fully rolls back. Aurora's PostgreSQL compatibility and ACID transaction guarantees make this reliable. A document store cannot enforce the referential integrity between batches, sale_items, products, and vendors that keeps the stock numbers honest.

Multi-tenant isolation is enforced at the query level. Every server action scopes its queries to the authenticated user's store_id. It is structurally impossible for one store's data to appear in another store's context.

Authentication: Better Auth with email and password, session-based. The first user to sign up becomes the store owner and triggers automatic store creation. Subsequent users join via invite tokens scoped to a specific store.

Frontend: Tailwind CSS v4 with a fully custom dark design system defined as CSS custom properties. All components use design tokens no hardcoded colour values in component code. shadcn/ui provides the accessible primitive layer (Sheet for slide-overs, Dialog for confirmations, Sonner for toasts).

PDF generation: @react-pdf/renderer, entirely client-side. No server route, no serverless function, no cold start, the PDF is generated in the browser from the sale data already on the page.

Deployment: Vercel with automatic preview deployments on every push to any branch and production deployment on merge to main. The Aurora integration is connected at the project level.


Challenges I Ran Into

The FEFO transaction across multiple batches The most complex engineering challenge was implementing FEFO correctly under concurrent load. If two cashiers process sales of the same product simultaneously, both transactions must resolve correctly without double-deducting from a batch. The solution uses a PostgreSQL transaction with a FOR UPDATE lock on the batch rows being modified, ensuring sequential resolution without deadlocks.

IAM token rotation for Aurora Static credentials for Aurora would have been simpler, but the IAM token authentication path was the right call for a multi-tenant SaaS. The challenge was ensuring the token rotation happened at the connection pool level without closing and reopening connections on every request. The solution generates a fresh IAM token on pool initialisation and wraps it in a connection event listener that refreshes before expiry.

Multi-currency without schema pollution Early designs stored currency alongside every price field. The better solution was storing the currency code once on the stores table and formatting all monetary values at the display layer using a React context provider that wraps the entire dashboard. The database stores numbers; the UI decides how to present them.

Mobile POS experience The two-column POS screen (cart left, checkout right) works well on desktop but collapses to a challenging single-column flow on mobile where a cashier needs to switch between searching products and confirming payment. The solution was a tab-based mobile layout that preserves the cart state across the search and checkout tabs.


Accomplishments That I'am Proud Of

The FEFO engine works. Under real conditions with multiple batches of the same product at different cost prices and expiry dates, the deduction logic correctly identifies and depletes the oldest batches first, records each batch contribution as a separate sale_item row, and leaves a complete audit trail.

No hardcoded credentials anywhere in the codebase. IAM token auth via RDS Signer from day one, the same security posture an enterprise would require.

The receipt PDF. A store owner can complete a sale and hand a printed receipt to a customer within 10 seconds. The PDF looks professional with store name, address, itemised list, payment details and is generated entirely in the browser with no server dependency.

Multi-tenant isolation that is provably correct. Every query in the codebase is scoped to store_id. There is no route or server action that returns data across store boundaries.

It runs on a tablet. The mobile layout was tested on a basic Android tablet, the device most likely to be at the counter of a small Nigerian supermarket. The POS screen, intake form, and alerts page all work correctly on a 7-inch touchscreen with a slow connection.


What I Learned

The biggest learning was the importance of the data model as the product. Trova's core differentiator is the batch-level tracking with FEFO deduction, it is not a feature in the traditional sense. It is a schema decision. The choice to have a batches table that records qty_remaining separately from products.stock_qty, and to deduct from batches during sales rather than just decrementing a number, is what makes the expiry tracking, consignment separation, and cost-per-unit analytics possible. Everything interesting in the product flows from that one structural decision.

The second learning was about the Aurora PostgreSQL + Vercel integration. The integration is genuinely production-ready from the first deployment. IAM token authentication, automatic credential injection, Serverless v2 scaling, these are not things you configure. They are things that work. The time saved not managing database infrastructure was entirely reinvested into product logic.


What's Next for Trova - IMS

Desktop-native offline client: The most-requested capability from real store owners in the target market is offline operation. The next phase is a Tauri desktop app with a local SQLite database that syncs to Aurora when internet is available, using a conflict-resolution strategy based on event timestamps.

Barcode scanner and receipt printer integration: USB HID barcode scanners and ESC/POS thermal printers via the Web Serial API, enabling a full POS hardware stack without proprietary software.

Subscription billing: Paystack integration for the Nigerian market (card and bank transfer) enabling the freemium-to-paid conversion. The free tier covers a single location and 3 users. The Business tier unlocks multi-location, advanced analytics, API access, and unlimited users.

WhatsApp low-stock alerts: Direct WhatsApp notifications via the Meta Cloud API when stock falls below reorder level, meeting store owners where they already are rather than expecting them to check a dashboard.

Multi-location support: A single owner account managing multiple store locations, with consolidated analytics across all locations.


Third-Party Integrations

Trova integrates the following third-party tools, all used in accordance with their respective open-source licenses and terms of service:

Integration License Usage
Amazon Aurora PostgreSQL (via Vercel) AWS Service Terms Primary database for all application data
Better Auth MIT Session-based authentication, invite token management
Tailwind CSS v4 MIT Utility-first styling framework
shadcn/ui MIT Accessible UI component primitives
@react-pdf/renderer MIT Client-side PDF receipt generation
papaparse MIT Client-side CSV export for analytics
lucide-react ISC Icon library
Inter (Google Fonts) OFL-1.1 Primary UI typeface
JetBrains Mono (Google Fonts) OFL-1.1 Monospace typeface for SKUs and numbers

No proprietary APIs with usage restrictions are used. No user data is sent to any third-party analytics or tracking service.


AWS Database Usage

Database used: Amazon Aurora PostgreSQL

How it is integrated:

  • Provisioned through the Vercel Marketplace Aurora integration
  • Connected using IAM token authentication via AWS RDS Signer (no static credentials)
  • Aurora Serverless v2 with 0.5–4 ACU range for cost-efficient scaling
  • All 8 domain tables live in Aurora: stores, users, categories, vendors, products, batches, sales, sale_items
  • FEFO inventory deduction runs as an atomic Aurora transaction
  • Multi-tenant isolation enforced via store_id scoping on every query

Built With

Share this project:

Updates