How We Built an eCommerce Mobile App That Scaled to 1M+ Active Users
Last Updated on August 31, 2026
Quick Summary
This insight explains how we built an eCommerce mobile app for a client expecting forecastable traffic spikes. It covers the constraint that shaped the design, why checkout was built as asynchronous, the five steps we ran before launch, and how the app performs at more than 1 million active users.
Table of Contents
Introduction
Teams often assume that an underperforming eCommerce mobile app has an acquisition problem at the top of the funnel. In most cases, the loss happens much later, in the final few taps before a payment clears. Baymard Institute places the average cart abandonment rate at 70.22 percent across 50 separate studies, and mobile checkouts run higher than that.
The cause is structural rather than cosmetic, and it appears in much the same way across eCommerce apps. Browsing is read-heavy, so product listings and search cache well and hold their performance as traffic climbs. Checkout operates under different conditions and does not benefit from the same shortcuts. It carries session state, depends on payment gateways outside the team’s control, and writes to inventory records that other shoppers are updating. At sufficient volume a familiar pattern appears, with catalog screens staying fast while order confirmations begin to time out.
Our client anticipated this pattern well before the first sprint of the project began. They had campaign-led growth planned, with traffic spikes they could forecast against a calendar rather than guess at. They came to Bacancy wanting the checkout path designed for that load from the start, not corrected after a failed sale weekend. What follows is the requirement they brought us, how we built the eCommerce mobile app around it, and where it stands today at more than 1 million active users.
Where the Requirements Ended and the Constraints Began
The brief we received was narrower than most app briefs, and that turned out to be an advantage. The client was not asking us for a feature list or a set of screens. They wanted an eCommerce mobile app that would keep accepting orders during peak traffic, on a catalog they expected to grow several times over within the first year.
Three requirements sat underneath that goal:
Payment coverage without duplicate orders- Cards, wallets, and cash on delivery, with retry behaviour that would not charge a customer twice when a gateway timed out mid-transaction.
One codebase for iOS and Android- The client’s internal team was small and could not carry two separate release cycles once we handed the app over.
Operational visibility in real time- The team needed to see checkout failures as they occurred, rather than reconstructing them from support tickets an hour later.
The constraints came from the systems the business was already running. Inventory and order data lived in an existing ERP that we could integrate with but not replace, and it had been sized for back-office use rather than consumer traffic. We benchmarked its order-write endpoint early in the engagement and found that response times held at low concurrency, then degraded once we pushed request volume toward the levels the campaign plan implied. The launch window imposed a second limit, since it was tied to a seasonal campaign on a date that could not shift.
Checkout needed to accept orders during the exact hours when the ERP was least likely to respond quickly. Once we accepted that it could be slow or briefly unavailable during a sale, order capture could not depend on that system being reachable at the moment a shopper paid. We settled this before any architecture work began, and it explains why the checkout flow was designed as asynchronous rather than synchronous.
How We Chose the eCommerce Mobile App Stack: React Native, Node Services, and Postgres
Stack decisions on this project were settled by the constraints in the previous section rather than by preference. Each choice had to answer a specific requirement, and anything that did not was left out of scope for the first release.
Layer
What we chose
Why
Mobile client
React Native
The single-codebase requirement ruled out separate native builds, and it left the client's internal team with a stack they could maintain after handover. Payment SDK integration and the barcode scanner were still written natively on both platforms and bridged into the shared codebase.
Backend services
Node, split by domain
Catalog, cart, checkout, and order processing run as separate services rather than one application. Catalog and cart are read-heavy and scale horizontally, while checkout and order processing carry different load patterns and needed to scale on their own schedule.
Primary datastore
Postgres
Order capture and stock reservation both require transactional guarantees. Reservations are written inside a transaction that locks the stock row before decrementing it, so two shoppers cannot claim the same unit during a sale.
Cache and queue
Redis
Holds session state and cart contents, and carries the queue that moves confirmed orders toward the ERP once that system is available.
Our 5-Step Process for Building an eCommerce Mobile App That Holds Up Under Load
Holding up under load is a design outcome rather than a testing outcome, which is why these five steps start well before the first load test runs. Each one produced a number or a rule that the next step was measured against, and together they define what the eCommerce mobile app had to survive before it went anywhere near real traffic.
Step 1: Load Modelling From the Campaign Calendar
We started with the client’s marketing plan rather than a generic traffic curve. Campaign emails, push notifications, and paid campaigns all land at known times, and each one produces a spike with a recognisable shape. From that we derived peak concurrent sessions, expected add-to-cart rate, and the number of checkout attempts per minute the system needed to absorb. Those figures became the target that every later step was tested against.
Step 2: Defining Failure and Degradation Rules
Before any service code was written, we decided what the app should do when a dependency stops responding. Search failing does not have to stop a shopper who has already reached checkout. A slow ERP should not surface to the customer at all. A payment gateway timing out has to produce one clear outcome rather than an ambiguous one. Writing these rules down early made error handling a design decision instead of something assembled during bug fixing.
Step 3: Instrumentation and Monitoring
Instrumentation went in with the first services, not after them. Every stage of the checkout path emits its own timing and outcome, so the funnel can be read as a sequence rather than a single success rate. Because our full stack developers owned both the client and the services, the app-side events and the server-side traces were designed to line up rather than being stitched together afterwards. The client’s team received dashboards showing checkout attempts, payment authorisations, order writes, and queue depth on one screen, which is how the real-time visibility requirement from the brief was met.
Step 4: Load and Stress Testing
Load testing ran against the model built in Step 1, then deliberately exceeded it. We ran the expected peak, then pushed to roughly three times that figure to find where the system broke and to confirm that it broke in the way the Step 2 rules described. Stress runs also covered dependency failure directly, including a stalled ERP endpoint and a gateway returning errors under load, since those conditions rarely appear in a clean load test.
Step 5: Staged Rollout and Rollback Planning
The app went live before the campaign, not with it. We released to a limited user share through a phased store rollout, watched the checkout funnel against the modelled numbers, and widened the release from there. Backend services each carried a documented rollback path, and new checkout behaviour sat behind feature flags, so any regression could be switched off without waiting on an app store review. The queue between checkout and the ERP added a third option, since downstream processing could be paused without stopping order capture.
Building an eCommerce App That Has to Reach Scale?
Hire mobile app developers from Bacancy Technology who treat scale as an architecture decision in the first sprint, not a capacity fix applied later.
How We Built the Checkout and Payment Flow
Checkout is where the earlier constraints meet in one place. It has to authorise a payment through a gateway we do not control, reserve stock without over-selling it, and reach an ERP that slows down at the hours this flow matters most. The design question was which of those three a shopper should ever have to wait for.
Separating Order Capture From Order Processing
The customer journey ends at payment confirmation. Once the gateway authorises, we write the order to Postgres, reserve stock in the same transaction, and confirm to the app. The ERP write, fulfilment notification, and invoice generation run from the queue afterwards.
Handling Gateway Timeouts Without Duplicate Orders
A timeout is not a failure, it is an unknown. Each checkout attempt carries an idempotency key that the payment service reuses on every retry. If a retry succeeds where the original response was lost, the gateway returns the existing authorisation instead of charging the card again, and the same key prevents a second order being written.
Reserving Stock Under Concurrent Load
Stock reservation happens inside the transaction that writes the order, with the row locked before it is decremented. Two shoppers claiming the last unit are serialised by the database, and the second receives an out-of-stock response rather than a confirmation the business cannot honour. Reservations from failed payments expire on a timer.
Reconciling the Queue With the ERP
A worker delivers queued orders with exponential backoff, so a slow ERP produces delay rather than loss. Anything that exhausts its retries lands in a dead-letter queue for the client’s team to inspect and replay. A scheduled reconciliation job compares Postgres records against the ERP, which catches writes that succeeded without an acknowledgement.
How the eCommerce Mobile App Performs at 1M+ Active Users
The app crossed 1 million active users without a change to the architecture described above. There was no earlier version to measure against, so the comparison below runs from design target to production behavior.
Design Target
What the app does at 1M+ users
Absorb campaign-peak checkout attempts
Holds through the modelled peak and above it, with degradation following the rules set in Step 2 rather than producing an outage
No duplicate orders from gateway retries
Idempotency keys prevent both a second charge and a second order write when a response is lost
ERP slowdown invisible to shoppers
Order capture completes before the ERP is involved, so downstream delay never reaches the customer
Stock accuracy under concurrent checkout
Row-level locking serialises competing reservations, and expired holds return to the catalog automatically
Real-time visibility for the client's team
Checkout funnel monitored stage by stage, with most incidents now handled in-house without escalation
Traffic has exceeded the modelled peak more than once since launch. On each occasion the system shed load the way the failure rules describe, degrading non-essential paths while checkout stayed open. The ERP still slows when a campaign lands, which is what the early benchmark said it would do, but order capture never waits on it. The client’s team now runs day-to-day operations without us, catching funnel problems while a campaign is running rather than reviewing them after it ends.
What We Are Carrying Into the Next eCommerce Build
Each of these lessons comes from decisions that had a direct impact on stability, scalability, and operational clarity once the system went live. They are not theoretical best practices but practical adjustments shaped by real traffic, real failures, and real business constraints. Carrying them forward means starting the next eCommerce build with clearer assumptions, fewer reactive fixes, and a system that is designed to handle pressure from day one rather than adapting to it later.
Benchmark legacy dependencies first- Measuring the ERP order-write endpoint before design began shaped every architectural decision that followed. Without those figures, the queue would have been a defensive assumption rather than a justified one.
Define failure behaviour at design time- Establishing what the application does when a dependency stalls turned error handling into a design activity. Teams that defer this make the same decisions later, under incident pressure, in whatever form the fix happens to take.
Instrument each stage separately- An aggregate success rate confirms that something has gone wrong. Stage-level timing identifies where, which is what enabled the client’s team to assume daily operational ownership.
Model traffic from the campaign calendar- Generic load curves understate the shape of promotional spikes. Marketing schedules are available months in advance and provide a far more accurate basis for capacity planning.
Deliver reconciliation alongside the queue- Ours followed later, leaving a period in which dead-lettered orders required manual intervention. Asynchronous processing and reconciliation form a single delivery guarantee and belong in the same release.
Conclusion
Scaling an eCommerce mobile app past 1 million active users had less to do with raw capacity than with deciding early what a shopper should never wait for. Payment authorisation, stock reservation, and order confirmation stayed on the critical path. Everything downstream, including the ERP the business could not replace, moved behind a queue. That decision, settled before any architecture work began, is what kept order capture available on the days the surrounding systems were under the most strain.
The rest followed from measurement rather than assumption. We modelled traffic from the campaign calendar, benchmarked the systems we had to integrate with, and tested against those numbers instead of a generic curve. As a mobile app development company, our team at Bacancy Technology builds this way because an app that holds up under load is designed that way from the first sprint.
Timelines depend on catalog complexity and how many external systems sit in the order path, but the load work itself is not what extends a project. Building for peak traffic from the first sprint costs days in modelling and benchmarking, whereas retrofitting it after a failed campaign usually means reworking the checkout path entirely.
Client-side framework choice has very little to do with how an app behaves under load, since scale is determined by the backend services, database, and integrations behind it. React Native handled a million users here without issue. The relevant trade-offs are around native SDK access and device performance, not throughput.
It should sit behind a queue rather than in the order path. Once payment is authorised and stock is reserved, the order is confirmed to the shopper and queued for the ERP separately. A slow or unavailable ERP then produces a processing delay rather than a lost sale.
Hardik Patel
Technical Lead at Bacancy
Specializes in .NET Core, Web API, MVC, SQL, and Entity Framework, with a focus on clean code and scalable architecture