Case Study · 01

Nivo

Slide 1Slide 2Slide 3
01
Executive Summary

Nivo is a Turborepo monorepo powering five apps — a NestJS API (190+ source files, 120+ REST endpoints), a Next.js 14 admin + POS, a customer-facing storefront, and two React Native mobile apps — all sharing 84 TypeORM entities across isolated PostgreSQL databases per tenant. I designed and built the database-per-tenant middleware with in-memory connection pooling (each tenant's DataSource is created once and reused for every request after), a 3-level cascading pricing engine that eliminates thousands of redundant price rows, an offline-first POS sync pipeline via Dexie.js, and Nibbit — an agentic AI assistant powered by Gemini with 14 function-calling tools that drafts purchase requisitions and supplier emails autonomously. Nivo began as my bootcamp capstone: a multi-tenant POS MVP I shipped in three weeks as backend lead and project manager of a team where I was the only trained developer. This monorepo is its second life — a ground-up solo rebuild, every commit mine, of what we used to call "the base of the system of our dreams".

02
The Challenge

Multi-branch shoe retail is one of the hardest inventory problems in physical commerce. Every product explodes into a matrix — size × color × branch — that quickly multiplies into hundreds of thousands of possible SKU combinations; franchises sharing one platform must never see each other's records; the counter cannot stop selling when the connection drops; and the final price of a single shoe depends on global landed-cost bases, per-branch cost overrides, and per-list margin exceptions — all under Mexico's strict CFDI electronic-invoicing rules. Nivo was built to hold that entire problem at once, end to end.

03
Architecture

Database-per-tenant pattern built on NestJS. A Master PostgreSQL database stores tenant records and Stripe subscriptions. On every request, a custom NestJS middleware extracts the subdomain (e.g. acme.nivo.app), queries the master DB to resolve the tenant, and hands a dedicated TypeORM DataSource for that tenant's isolated database to the request pipeline. Connections are pooled in-memory via a Map<string, DataSource>, so the cost of opening a tenant's connection is paid once — not on every request. Async workloads — tenant provisioning, low-stock alerts, invoice generation, report exports — are handled by BullMQ workers backed by Redis.

gatewayservicedbcacheexternal
Tenant Isolated ScopeHTTPSreq pipelineresolve tenantget / createinjects DSinjects DStool queriescascade queryprompt ↔ toolsqueuesClientBrowser / AppNestJS APIacme.nivo.appRedisBullMQ · SocketsTenant Resolversubdomain → dbMaster DBTenants · PlansConn. ManagerDataSource PoolNibbit AIAgentic · 14 ToolsPricing EngineCascade FallbackTenant DBIsolated PostgresGemini APIGoogle · External
04
Key Features
Multi-Tenant · POS

Offline-First Point of Sale

Every sale is written directly to IndexedDB via a Dexie.js database (NivoOfflineDB) using UUID primary keys, so the cashier never waits for a network round-trip. Pending sales are queued locally with a synced: false flag. When the browser fires the online event, a listener replays the entire queue via POST /sales/sync. The API processes each sale inside its own database transaction — validating stock, deducting inventory, creating payment records, and triggering async stock evaluation via setImmediate — then returns a per-item status report. UUID keys guarantee idempotency across offline and online sessions.

Offline-First Point of Sale
Pricing · Engine

Cascading Fallback Pricing

The PricingService.calculatePrice() method resolves the final sale price through a strict 3-level fallback cascade. Step 1 — Purchase Cost: reads branch_variant_overrides for a branch-specific cost; falls back to the variant's global cost field. Step 2 — Base Price: multiplies cost by (1 + landed_cost_%), where landed cost is resolved from branch_setting_overrides, then tenant_settings, then zero. Step 3 — Sale Price: multiplies the base by (1 + margin_%), where margin is resolved from variant_price_margins for a specific price list, then the price list's default_margin_percentage. This eliminates thousands of redundant price rows — only exceptions are stored.

Cascading Fallback Pricing
Inventory · Matrix

Matrix Variant Generation

The system models the physical world of footwear through a relational schema of size_groups (e.g. Men, Women, Kids), size_systems (MX, US, EU), and size_equivalencies that map each size across systems. Product variants store dynamic attributes as a JSONB column — { "Color": "Black", "Talla MX": "26" } — replacing rigid fixed columns. The frontend crosses active size groups with available colors to auto-generate the full Cartesian product of variants: a shoe in 3 colors and 12 sizes produces 36 SKUs with zero manual data entry, eliminating human error at cataloging time.

Matrix Variant Generation
AI · Agentic

Agentic AI with Human-in-the-Loop

Nibbit is the platform's built-in AI agent, powered by Gemini (gemini-2.0-flash) via @google/generative-ai with 14 registered function declarations. Read-only tools query sales summaries, low-stock items, branch comparisons, and product catalogs by executing raw SQL against the tenant's isolated database. Mutation tools — draft_auto_requisition and draft_supplier_emails — scan inventory deficits, generate purchase requisition drafts, produce PDF attachments via Puppeteer, and compose supplier emails using a second Gemini call. Nibbit never executes final mutations: every draft is surfaced to the human administrator for review and approval before becoming a real Purchase Order.

Agentic AI with Human-in-the-Loop
Mobile · Edge

Omnichannel Native Ecosystem

Two React Native / Expo Router apps operate as tenant-aware API clients sharing @nivo/types. The B2B app (mobile-b2b) transforms the device into a high-speed barcode scanner via expo-camera's CameraView — scanning EAN-13, UPC-A, and Code128 barcodes with haptic feedback (expo-haptics) for inventory audits, and tracks live delivery coordinates via expo-location's watchPositionAsync (high accuracy, 20 m interval) with proof-of-delivery photo capture. The B2C app (mobile-b2c) integrates @stripe/stripe-react-native for checkout, uses Haversine distance to auto-select the nearest pickup branch from the user's GPS position, and surfaces a loyalty ledger with tiered QR codes (Bronze → Silver → Gold → Wholesale) that auto-brighten the screen via expo-brightness for instant in-store scanning. Both apps use @tanstack/react-query for real-time cache consistency against the multi-tenant API.

Omnichannel Native Ecosystem — B2B front
Omnichannel Native Ecosystem — B2B back
tap to flipB2B
Omnichannel Native Ecosystem — B2C front
Omnichannel Native Ecosystem — B2C back
tap to flipB2C
05
Implementation
1@Injectable()
2export class TenantConnectionMiddleware implements NestMiddleware {
3 constructor(
4 @InjectRepository(Tenant)
5 private readonly tenantRepo: Repository<Tenant>,
6 private readonly connectionManager: TenantConnectionManager,
7 ) {}
8
9 async use(req: Request, _res: Response, next: NextFunction) {
10 const subdomain = this.extractSubdomain(req);
11 if (!subdomain) return next();
12
13 // 1. Resolve tenant from master DB
14 const tenant = await this.tenantRepo.findOne({
15 where: { subdomain, is_active: true },
16 });
17 if (!tenant) throw new NotFoundException(`Tenant "${subdomain}" not found`);
18
19 // 2. Attach isolated DataSource to the request
20 req.tenant = tenant;
21 req.tenantConnection = await this.connectionManager.getConnection(
22 tenant.database_name,
23 );
24 next();
25 }
26
27 private extractSubdomain(req: Request): string | null {
28 const header = req.headers['x-tenant-id'] as string;
29 if (header) return header;
30 const parts = req.hostname.split('.');
31 return parts.length >= 3 ? parts[0] : null;
32 }
33}

Resolves subdomain to an isolated TypeORM DataSource, pooled in-memory via Map<string, DataSource> — the connection cost is paid once per tenant, not per request.