All work

02 / Case Study

Multi-Tenant Seller Marketplace

Seller operations, order management, and Shopify synchronisation for a multi-tenant marketplace, rebuilt on NestJS with a migration of 800+ sellers and zero data loss.

Period2022 — 2025
RoleBackend Lead
800+sellers migrated, zero data loss
2.7Mrecords re-indexed
−25%query latency

The problem

The marketplace runs hundreds of independent sellers on one platform — each with their own products, orders, subscriptions, and payout terms — synced against Shopify as the storefront of record. The existing system had grown organically; migrating it onto a new schema without breaking a single seller's live orders, and without the query latency that comes with millions of rows across shared tables, was the core problem.

Architecture

NestJS with TypeORM over MySQL for the transactional core, MongoDB for a subset of flexible document data, and a dedicated sync layer reconciling seller catalogues and orders against the Shopify Admin API via webhooks. Inbound Shopify data lands in a raw dump table before it is mapped into live order entities, so a re-run of a sync stages rather than corrupts — the staging table is the idempotency boundary. A status mapper translates Shopify's and each carrier's own vocabulary into one internal order-status model, which is what keeps the rest of the system from growing per-integration conditionals. Carriers themselves are modelled as data — a shipping-partner entity plus a per-seller onboarded-partner join — so adding one is configuration rather than a branch. Role-based access control is modelled directly in the schema (userRole / userPermission entities) rather than bolted on, so the same authorization path serves sellers, staff, and admin.

Marketplace sync, core, and downstream providersShopify webhooks and a sync layer keep seller catalogue and order data current in the marketplace core, which fans out to two payment providers split by purpose, two logistics carriers, and an asynchronous export queue for seller reporting.ShopifyAdmin APISync Layercatalogue + ordersMarketplace CoreNestJS · MySQL · TypeORMRazorpaysubscriptionsCashfreesettlements + invoicesDelhivery / ShiprocketfulfillmentExport Queueasync seller reportswebhookswrites back

Decisions

Migrate seller-by-seller, not table-by-table

800+ sellers — products, orders, and subscriptions — were moved with full validation per seller rather than a single big-bang schema cutover, so a bad record surfaced against one seller instead of failing the whole migration.

Index for the queries that actually run

Schema and indexing were reworked against real query patterns across roughly 2.7 million records, cutting query latency by 25% — the kind of gain that only shows up once you profile production traffic rather than guess at indexes upfront.

Payments split by purpose

Razorpay handles subscription billing; Cashfree handles invoice payments and automated seller settlements — two different money paths kept deliberately separate rather than forced through one abstraction.

Instrument before optimising

OpenTelemetry was wired in with a custom interceptor and a tracer decorator that could be dropped onto specific methods, feeding Signoz. That is what turned the latency work from guesswork into targeted fixes — the 25% figure is measured against traces, not inferred from a stopwatch.

Long-running reports leave the request path

Seller exports run through a dedicated queue with its own entities, so generating a large report is a job with a status a seller can poll rather than a request that times out.

Stack

Service
NestJSTypeScriptMySQLTypeORMMongoDB
Commerce
Shopify Admin APIWebhooksStaged sync + dump table
Payments
RazorpayCashfree
Logistics
DelhiveryShiprocketMulti-carrier abstraction
Async
Export queueScheduled tasks
Observability
OpenTelemetrySignozCustom tracer decorator
Cloud
AWS SQSSESS3Secrets Manager