Back to Blog
Engineering7 min read

How to Build an E-Commerce Platform with Next.js, Stripe, and PostgreSQL

Why Build Custom Instead of Using Shopify?

Shopify is the right answer for most straightforward retail use cases. But custom e-commerce makes sense when: you have complex product configuration (custom orders, B2B pricing tiers, subscription products mixed with one-time purchases), specific integration requirements with ERP or inventory systems, or you need the storefront to be part of a larger application rather than a standalone store. I have built custom platforms in all three scenarios.

Product Catalog Schema

The product schema is the foundation. Get it right and everything else is easier. Use a products table with variants table linked by foreign key — a t-shirt is one product with variants for size and color. Store pricing in the variants table, not the product. Use JSONB for flexible attributes that vary by product type. Sync prices to Stripe Products and Prices so checkout always uses the source-of-truth price from Stripe, not your database.

-- Core schema
CREATE TABLE products (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  description TEXT,
  category_id UUID REFERENCES categories(id),
  attributes JSONB DEFAULT '{}'
);

CREATE TABLE variants (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  product_id UUID REFERENCES products(id),
  sku TEXT UNIQUE NOT NULL,
  price_cents INTEGER NOT NULL,
  stripe_price_id TEXT,
  inventory_count INTEGER DEFAULT 0,
  attributes JSONB DEFAULT '{}'
);

Cart Implementation

Store the cart in a database table linked to a session ID (for guests) or user ID (for logged-in users). This allows cart persistence across devices and browsers, which significantly reduces abandonment. Merge guest carts on login. Validate inventory availability when items are added and again at checkout — never at display time, as that creates false scarcity signals.

Checkout with Stripe

Use Stripe Checkout Sessions for PCI compliance — card numbers never touch your server. Create the session server-side with line items from your cart, redirect to Stripe's hosted page, and handle the result via webhooks. The checkout.session.completed webhook triggers order creation, inventory decrement, and fulfillment notification — all in a database transaction.

Inventory Management

Use PostgreSQL advisory locks when decrementing inventory to prevent overselling under concurrent load. Decrement inside the webhook handler transaction so inventory and order creation are atomic. Set up a low-stock alert that fires when inventory drops below a threshold. For products with zero inventory, show "out of stock" and offer a restock notification signup.

Order Management

Orders need a clear state machine: pending → confirmed → processing → shipped → delivered, with cancelled and refunded as terminal states from any step. Each state transition triggers appropriate customer notifications and fulfillment actions. Store tracking numbers and carrier information against shipments, not orders, to support split shipments.

I have built custom e-commerce platforms for clients in fashion, industrial supply, and digital products. Get in touch to discuss your e-commerce requirements.

E-CommerceStripeNext.jsPostgreSQL

Hire me for similar projects

Looking for a developer who can build what you just read about? Let's talk.

Get in Touch