Skip to content
TECH MANIAC
ALL WORK
FASHION RETAIL · COMMERCE + ERP

VIOR

A storefront and the double-entry ERP behind it, built as two deliberately separate systems.

LIVE · PUBLIC STOREFRONT

VIOR.COM.BD

221 ms → 4 ms

Server response time

15 → 390

Requests per second

45

Database tables in the ERP

VIOR — The storefront, live on vior.com.bd
VIOR — Collection page — filters by colour, size and tier
VIOR — Product page — variants, size guide and Cash on Delivery
VIOR — Order tracking by code and phone

THE STOREFRONT, LIVE ON VIOR.COM.BD

THE PROBLEM

An online-first fashion brand needs two things that are usually bought separately and never quite fit: a storefront that sells, and a back office that knows what was sold, what it cost, what is owed and what is left in stock.

The usual shape is a hosted storefront plus accounting software, with a person in the middle retyping numbers from one into the other. That person is the single point of failure, and their mistakes surface weeks later as books that will not reconcile.

We wanted one truth. Orders placed in the store had to become journal entries without anyone retyping them, stock had to move in both systems at once, and the courier's delivery confirmation had to settle the payment and close the loop by itself.

WHAT WE BUILT

STOREFRONT

  • Cash-on-Delivery checkout built for the local market, guest-friendly
  • Customer accounts with email/password and Google sign-in
  • Loyalty points — an append-only ledger, earned on delivery, redeemable at checkout as a tax-correct discount
  • Category-bound size charts resolved from the catalog rather than hardcoded
  • Full-text search, order tracking by code and phone, and account order history
  • Server-side conversion tracking, with purchase events deduplicated between browser and server

ERP

  • Double-entry ledger with sixteen registered posting rules
  • Supplier bills, payments, dues and a cheque register
  • Raw inventory and purchasing with moving-average costing
  • Production runs converting raw material to finished goods, with wastage absorbed
  • Courier integration with parcel booking and delivery webhooks
  • SMS with delivery receipts and automatic retry
  • Financial reports — trial balance, P&L, balance sheet, VAT, subledgers

SHAPE OF IT

The store and the ERP are separate repositories, databases, cache instances, runtimes and service units. The only runtime link is a localhost API call for synchronisation. If the ERP is down, the store keeps selling — which is the whole point of separating them. Traffic arrives through a CDN, hits nginx, and is served by a prerendered storefront that in the common case touches neither the commerce API nor the database.

ON MISSSYNC 30SSHOPPERbrowserCDNedge cacheNGINXorigin guardSTOREFRONTNext.js · ISRCOMMERCE APIVendurePOSTGRESstoreERPNestJSPOSTGRESbooksCOURIERwebhooks
TWO SYSTEMS, ONE LOCALHOST LINK — THE STORE NEVER DEPENDS ON THE ERP BEING UP
ONE WAYBOTH WAYSPERIODIC
VendureNestJSNext.jsPostgreSQLValkeyCloudflare R2

DECISIONS THAT MATTERED

The parts that were not obvious.

01

The user never picks an account, and never writes a journal entry

Business owners do not think in debits and credits, and asking them to means the books are wrong within a month. Instead they post business documents — a bill, a payment, a production run — and balanced journals are generated from registered posting rules that reference accounts by semantic role rather than by ID. Adding a new kind of document means adding a rule file, not touching the ledger engine.

02

The balance invariant is enforced by the database, not by the application

A CHECK constraint on the journal line table rejects any row that is both a debit and a credit, or negative in either. A partial unique index guarantees exactly one account per semantic role. These are the two things that make role-based posting safe, and putting them in the schema means no application bug — present or future — can write books that do not balance.

03

Books are append-only

Corrections are written as mirrored reversal entries linked to the original, never as edits or deletes. An entry can be reversed exactly once, and a reversal cannot itself be reversed. Subledgers — supplier dues, customer dues, cash balances — are derived from posted journals rather than stored, so there is no cached balance that can silently drift out of agreement with the ledger.

04

The sync loop heals itself

Orders are pulled from the store every thirty seconds. The loop holds a re-entrancy lock, never throws, is idempotent by order ID, and pages backwards until it reaches a page it already knows. That last detail matters: a naive implementation that reads only the first page permanently loses any order pushed past it during an outage. This one recovers from an outage of any length without intervention.

05

Caching had to be fixed at the data layer, not the CDN

The storefront originally rendered every page dynamically, because a single session-cookie read opted the entire render tree out of static generation. Worse, each anonymous visit minted a session the render could not persist — two database writes per pageview, for visitors who never bought anything. Splitting reads into a session-free catalog path and a session-carrying shopper path fixed both at once.

06

Paid traffic gets cached too

Every ad click carries a unique tracking parameter, which lands in the CDN cache key — so paid traffic ran at a zero per cent cache hit rate while organic traffic sat at a hundred. A URL rewrite that runs before the cache lookup normalises the key without touching the visitor's address bar, so attribution still works and the edge absorbs the spike.

If the ERP is down, the store keeps selling. That is the whole reason they are separate systems.

WHERE IT LANDED

  • Server response time fell from 221 ms to 4 ms, and throughput rose from 15 to roughly 390 requests per second.
  • Database writes per anonymous pageview went from two to zero.
  • Fifty-one routes are prerendered where previously none were.
  • Ad-click traffic went from zero per cent edge-cached to one hundred.
  • The origin no longer answers requests that bypass the CDN, closing a route that would have allowed the catalog to be scraped or the API hammered directly.
  • The store moved from its build subdomain onto vior.com.bd, with the old host kept as a permanent redirect so nothing already indexed or shared was broken.