<prepcode />
← Back to Learn
Tutorial9 min read

Building a Real Storefront, Not a Three-Thousand-Word ORM Debate

Most e-commerce tutorials start with an ORM decision and end three thousand words later without a working checkout. This one is the opposite. The goal here is a real, working single-page storefront — product listing, cart, auth, and a checkout flow - built with tooling you can actually ship with on day one, not tooling you'll spend the next two sprints configuring.

Why Firebase, Specifically

Firebase earns its place in this stack because it collapses three normally separate backend concerns - authentication, a database, and hosting - into a single console. That's not a minor convenience. On most stacks, standing up auth, a database, and a deployment pipeline is three separate decisions, three separate services, and three separate places for something to misconfigure.

For a storefront that doesn't need bespoke backend logic on every request - which describes the large majority of product catalogs, cart flows, and account pages - collapsing those three concerns isn't cutting a corner. It's matching the amount of infrastructure to the actual size of the problem.

Firestore's document model is a good fit for the same reason. Products, carts, and orders all map cleanly onto documents without forcing a schema migration every time the catalog grows a new variant, a new attribute, or a new product type nobody planned for at the start. A relational schema would make you decide that structure up front and pay for changing your mind later. Firestore lets the shape of the data follow the shape of the catalog as it actually evolves.

An Intentionally Boring Architecture

The architecture here is boring on purpose, and that's a feature, not a limitation.

An Angular SPA talks directly to Firestore for reads - product catalog, categories, anything a customer is just browsing - and to Firebase Authentication for session state. No custom backend sits between the client and the data for these read paths, because none is needed; Firestore's security rules do the job a hand-rolled API layer would otherwise exist to do.

The one deliberate exception is payment. A thin Cloud Function sits in front of anything that touches money, because client-side code should never be trusted with the final say on what a customer gets charged. That's the whole architecture in one sentence: trust the client with reads, and put a server between the client and anything irreversible.

Cart Persistence You Didn't Have to Build

Cart state lives in a Firestore document keyed to the authenticated user, synced in real time. That design choice has a side effect worth calling out on its own: it gives you cart persistence across devices for free. A customer adds something to their cart on their phone during a commute and finds it waiting on their laptop that evening, with no extra code written to make that happen.

That's the kind of feature that would normally cost a sprint on a more conventional stack - session handling, a persistence layer, a sync strategy, edge cases around conflicting writes. Here, it falls out of the platform choice itself. You're not building cart persistence; you're just using Firestore the way it's meant to be used, and persistence comes along with it.

Where the Thin Cloud Function Actually Matters

Checkout is where the "thin Cloud Function" pattern earns its keep, because checkout is where a mistake costs real money instead of a bad user experience.

The client submits an intent - cart contents, shipping address, whatever the customer selected - and nothing more. A server-side function is the only thing authorized to calculate the final total, apply discounts, and confirm the charge. The client proposes; the server decides. That split isn't a technicality - it's the difference between a storefront a customer could manipulate with browser dev tools and one they can't.

Everything upstream of that function is UI. It can be fast, flexible, and optimistic about what it shows the customer, because none of it is authoritative. Everything at and after that function is the part you actually have to get right, and it's small and isolated enough that getting it right is tractable - a handful of server-side calculations instead of an entire application's worth of trust decisions scattered across the codebase.

A Pattern, Not a Toy

The result isn't a toy - it's a storefront pattern that holds up past the demo. What makes it hold up isn't the specific choice of Angular, or Firestore, or Cloud Functions; a different stack with the same shape would hold up just as well. What makes it hold up is that the boundary between "trust the client" and "trust the server" is drawn in the one place it actually matters: money changing hands. Everywhere else, the architecture stays out of the way. Right there, it doesn't move.

// keep going

Want this kind of thinking applied to your stack?

An appraisal is the fastest way to find out where your architecture is quietly costing you.