ecommerce·TBD·

LoHay: geo-aware marketplace shipped across web and two native apps

3-sided marketplace on one Django backend: buyer web, seller/inventory app, courier/delivery app. PostGIS search, AI discovery, multi-tenant theming — production-ready.

3
frontends on one backend
TBD
weeks from kickoff to first store live
TBD
stores onboarded at launch

The challenge

LoHay set out to launch a marketplace where buyers discover independent stores nearby, with AI helping them compare products. Three sides, three surfaces: buyers on the web, store owners managing inventory on a native app, couriers running deliveries on a native app. The technical surface was large from day one: geo search, AI-assisted discovery, multi-tenant branding, payments, payments splits between buyer / seller / courier. The team was small, the deadline was short, and every week of delay burned runway.

What we built

Delivered a unified platform where one Django backend serves all three surfaces. Web client (Next.js 15 + Tailwind v4 + shadcn/ui) for buyers to discover stores and purchase. Two Flutter apps — one for store owners to manage inventory and orders, one for couriers to claim and run deliveries — sharing a common Dart package (`lohay_core`) for platform infrastructure (API client, JWT, auth, geo, theme, settings). PostGIS powers geo search, MeiliSearch handles full-text, a wallet + ledger system with Stripe Connect runs payments with splits, S3 stores media. AI endpoints are pluggable via an OpenAI-compatible `ModelEndpoint` registry with role-based assignment (chat / embeddings / search intent), so the team can swap providers without redeploys. Multi-tenant theming lets each store keep its own brand without forking the codebase.

Tech stack

Django 6Next.js 15TypeScriptFlutterPostgreSQL + PostGISMeiliSearchRedisStripeAWS S3

TL;DR

  • One backend, three surfaces — buyer web, seller/inventory Flutter app, courier/delivery Flutter app on a single Django API.
  • Geo search at the core — PostGIS + MeiliSearch, served from the same endpoints to web and mobile.
  • Multi-tenant without forks — each store’s brand, catalog, and backoffice from one codebase.
  • AI without lock-in — pluggable provider layer lets the team swap models without redeploys.
  • Production-grade from day one — payments + splits, audit log, feature flags, OpenAPI, sanitization. No “we’ll add it later”.

The challenge

LoHay’s product vision was ambitious: a marketplace where buyers open the app, see nearby independent stores on a map, and chat with an AI assistant to find what they need. Each store needs its own brand, its own catalog, and its own backoffice.

That kind of product normally takes a large team and a long runway. LoHay had neither. The constraints were:

Constraint Why it mattered
Three surfaces, one codebase Buyer web + seller/inventory Flutter app + courier/delivery Flutter app, sharing one backend and one domain model.
Geo search everywhere PostGIS-style queries on every map view, on web and mobile.
Payment splits Each order splits the payout between seller and courier — Stripe had to handle this from day one.
AI without vendor lock-in Discovery had to work out of the box, but swap models without a rewrite.
Multi-tenant theming Each store with its own palette, logo, copy — no code duplication.
Production foundations on day one Payments, audit, flags, OpenAPI, sanitization. Not bolted on later.

Every week of delay was runway lost. There was no room for a fragile MVP that had to be rewritten once traction appeared.

What we built

Capability How
One backend, three surfaces Django 6 + DRF. Buyer web on Next.js 15 + TanStack Query. Seller/inventory + courier/delivery Flutter apps on the same API. Platform infrastructure shared in lohay_core.
Geo-aware discovery PostGIS (via django.contrib.gis) for spatial queries; MeiliSearch for full-text. Same endpoints serve web map and mobile.
Pluggable AI endpoints ai_providers.ModelEndpoint registry with OpenAI-compatible HTTP API. Roles (chat / embeddings / search intent) point at endpoints. Add or swap providers without code changes.
Multi-tenant theming theming/middleware.py + palette.py (allow-list + regex guard so palette values cannot escape CSS context). New tenant = content operation, not a deploy.
Wallet + ledger Per-store wallet with hold/release and margin economics. Stripe Connect as the payout gateway; the split rules live in our ledger, not in Stripe.
Production-grade from day one See the table below.

Production foundations

Concern What we shipped
Auth Google OAuth + JWT, full audit trail (django-auditlog).
Payments Stripe webhooks, idempotent handlers, S3 media storage.
Feature flags django-flags for dark launches and gradual rollouts.
API docs drf-spectacular generates OpenAPI from the DRF views, always in sync.
Security NH3 sanitizes any user-generated HTML on the way in.
i18n ES/EN first-class across web and mobile.

Results

The platform is in production with three live surfaces backed by one API: the buyer-facing web app, the seller inventory app, and the courier delivery app. The team can:

  • Onboard a new store by creating a tenant record — no deploy required.
  • Swap or A/B test AI providers without code changes downstream.
  • Roll out new features behind flags and measure impact before broad exposure.
  • Add a new surface (admin web, ops CLI, partner portal) without re-implementing domain logic.

Quantitative results are placeholders pending client confirmation — real numbers (time-to-launch, stores onboarded, transactions processed, conversion rate) will be wired in once the client signs off.

What’s next

Roadmap is open: B2B accounts, marketplace analytics, expansion to additional geographies. The architecture supports each without rewrites.


Building a multi-tenant product with a tight deadline and a small senior team? Book an intro call. We’ll tell you honestly whether we can help.

Have a similar project?

We help startups build the right thing, the right way. Let's talk about yours.