Skip to content
Nikesh Khatri
All work

Retail SaaS

Jul – Sep 2026

Independent product

Mero Pasal

Multi-tenant retail SaaS for Nepali shops — isolated per shop at the database level, with a storefront attached.

Platform “Tenants” console listing ten demo clothing shops with slug, plan, colour-coded status (Active, Trial, Past due, Suspended) and creation date.
One operator console provisions and oversees every shop on the platform, from trial through past due to suspended.

Role

Sole engineer — platform architecture, backend, frontend, infrastructure

Live site ↗

Stack

  • Django
  • DRF
  • PostgreSQL RLS
  • React
  • Vite
  • TypeScript
  • Celery
  • Redis
  • Docker Compose
  • Caddy
  • Tailwind CSS
  • Playwright
  • Vitest
  • Pytest

Evidence

Merged pull requests
113
Commits
493

The problem

What this had to solve.

Small Nepali retailers run on a paper khata for customer credit and a calculator for VAT. Off-the-shelf POS software assumes a Gregorian fiscal year, English-only staff and one shop per install. The platform had to give every shop its own subdomain and its own strictly isolated data, work on a cheap phone behind the counter, and produce a VAT register the tax office will accept.

Architecture

How it actually works.

Tenant isolationArchitecture
Both shops travel the same code path. Middleware resolves the subdomain and sets a session variable; PostgreSQL row-level security scopes every row from there. A query that forgets its tenant filter returns nothing rather than another shop's data.REQUESTENFORCEMENTRESULTshop-asubdomainshop-bsubdomainMiddlewareresolve tenantSET app.tenantsession GUCRLS POLICYrow filterShop A rowsShop B rowsa missed filter returns0 rows, not someone else’s

Both shops travel the same code path. Middleware resolves the subdomain and sets a session variable; PostgreSQL row-level security scopes every row from there. A query that forgets its tenant filter returns nothing rather than another shop's data.

What I built

My contribution.

  • 01

    Developed a multi-tenant retail SaaS with isolated shop subdomains, branch-aware staff permissions, bilingual UI, Bikram Sambat reporting and IRD-oriented billing.

  • 02

    Built mobile-first sales, catalog, inventory, purchasing, customer credit (khata), expense, payment, reporting, VAT-register and day-close workflows.

  • 03

    Added an online storefront with tenant-controlled merchandising, website pricing, offers, templates, OTP customer login, checkout, idempotent ordering and order tracking.

  • 04

    Implemented account security, device-session revocation, audit logging, tenant-aware rate limits, privacy-safe observability, typed OpenAPI contracts and permission-safe exports.

  • 05

    Created a platform-admin control plane for tenant provisioning, plans, subscriptions, feature flags, usage metering and audited support sessions.

The hard part

Tenant isolation that survives my own mistakes

Filtering every queryset by tenant works right up until one developer forgets one filter — and then shop A is reading shop B’s sales. I did not want the guarantee to depend on discipline, so isolation moved into PostgreSQL row-level security: middleware resolves the subdomain, sets a session variable, and RLS policies scope every row automatically. An accidental unfiltered query returns nothing instead of returning a competitor’s revenue. The genuinely tricky part was the escape hatches — background jobs, the platform-admin control plane and support sessions all legitimately cross tenants, so each needed an explicit, audited elevation path rather than a bypass someone could reach for casually.

Read the full note, with the code →

Interface

What it looks like in use.

Customer ledger for a demo customer: credit sales and receipts from July to September 2026 with debit, credit and running-balance columns, closing at रू 7,650.

Udharo (khata) as a real ledger: each credit bill and part-payment by cash, QR or eSewa rolls into a running balance.

A demo shop’s public storefront: the headline “Handmade comfort for the festive season”, a Dashain sale badge and a featured kurta.

Each shop gets its own storefront on its own host, selling from the same catalog as the counter.

Phone POS mid-sale: a kurta at a negotiated रू 2,450 inside an owner-set range and a jacket at रू 4,500, with Cash and QR buttons and “Complete · रू 6,950.00”.

A phone-first counter sale: staff can bargain only inside the owner’s price range, then settle with cash or QR.