Skip to content

🛒 E-commerce Store

Build a complete online store with product catalog, shopping cart, and secure checkout.

📖 Cross-Ecosystem Example

This is a complete tutorial demonstrating the standard Flowfull architecture pattern using flowfull-node for your custom e-commerce backend.

🎯 What You'll Build

A production-ready e-commerce platform with:

  • ✅ Product catalog with categories and search
  • ✅ Shopping cart (guest + authenticated)
  • ✅ Secure checkout with Stripe & PayPal
  • ✅ Order management and history
  • ✅ Saved payment methods
  • ✅ Inventory management
  • ✅ Admin dashboard
  • ✅ Email notifications

Time to complete: ~120 minutes


🛠️ Tech Stack

ComponentTechnologyPurpose
🔐 FlowlessManaged Service (pubflow.com)Authentication - User accounts, guest checkout
FlowfullNode.js (flowfull-node)Your custom backend - Products, orders, inventory APIs
🎨 Flowfull ClientsNext.js (@pubflow/nextjs)Your frontend - Storefront with SSR
💳 Bridge PaymentsManaged ServicePayment processing - Stripe, PayPal, saved cards

🏗️ Standard Architecture Pattern

  • Flowless = Authentication backend (managed, no code needed)
  • Flowfull = Your custom backend using flowfull-node (you write this)
  • Flowfull Clients = Your frontend using @pubflow/nextjs (you write this)
  • Bridge Payments = Payment backend (managed, API integration only)

🏗️ Architecture

Flow:

  1. Frontend (Next.js) → Your Backend (Flowfull/flowfull-node)
  2. Your Backend → Flowless for authentication
  3. Your Backend → Bridge Payments for payment processing
  4. Bridge Payments → Your Backend (webhooks for order confirmation)

📋 Prerequisites

  • Bun v1.0+ (Install) or Node.js 18+
  • Database - PostgreSQL, MySQL, or LibSQL/Turso
  • Pubflow account - Sign up at pubflow.com
  • Stripe account - Sign up at stripe.com
  • Code Editor - VS Code, Cursor, or your favorite editor
  • ✅ Basic knowledge of React and TypeScript

🚀 Step 1: Setup Services (10 min)

1.1 Create Flowless Instance

  1. Visit pubflow.com
  2. Sign up or log in
  3. Click "Create Flowless Instance"
  4. Name it: store-auth
  5. Enable guest accounts for guest checkout
  6. Configure OAuth (Google, Facebook) for social login
  7. Copy your Bridge Secret

Your Flowless URL will be:

https://store-auth.pubflow.com

📖 Learn More

For detailed Flowless setup, see Flowless Getting Started

1.2 Create Bridge Payments Instance

  1. Create Bridge Payments instance at pubflow.com
  2. Add Stripe credentials
  3. Add PayPal credentials (optional)
  4. Configure webhooks
  5. Copy your Bridge Payments URL and API Key

⚡ Step 2: Setup Flowfull Backend (10 min)

2.1 Clone flowfull-node (Official Starter Kit)

📦 Official Starter Kit

flowfull-node is the official, production-ready starter kit maintained by the Pubflow team.

View Documentation →

bash
# Clone the official flowfull-node template
git clone https://github.com/pubflow/flowfull-node.git store-backend
cd store-backend

# Install dependencies
bun install
# or: npm install

2.2 Configure Environment Variables

Copy the example environment file:

bash
cp .env.example .env

Edit .env with your configuration:

env
# Flowless Configuration
FLOWLESS_API_URL=https://store-auth.pubflow.com
BRIDGE_VALIDATION_SECRET=your_bridge_secret_here

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/store

# Stripe (via Bridge Payments)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Bridge Payments
BRIDGE_PAYMENTS_URL=https://your-payments.pubflow.com
BRIDGE_PAYMENTS_API_KEY=your_api_key_here

# Server
PORT=3001
NODE_ENV=development

# Optional: Cache
REDIS_URL=redis://localhost:6379

🔧 Environment Variables

  • FLOWLESS_API_URL: Your Flowless instance URL from Step 1.1
  • BRIDGE_VALIDATION_SECRET: Bridge secret from Pubflow dashboard
  • STRIPE_SECRET_KEY: Your Stripe secret key from Stripe Dashboard
  • BRIDGE_PAYMENTS_URL: Your Bridge Payments instance URL from Step 1.2

📊 Step 3: Database Schema (15 min)

Create schema.sql:

sql
-- Products
CREATE TABLE products (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(500) NOT NULL,
  slug VARCHAR(500) UNIQUE NOT NULL,
  description TEXT,
  price_cents INTEGER NOT NULL,
  compare_at_price_cents INTEGER,
  images JSONB DEFAULT '[]',
  category VARCHAR(100),
  stock INTEGER DEFAULT 0,
  sku VARCHAR(100) UNIQUE,
  active BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Orders
CREATE TABLE orders (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id VARCHAR(255),
  guest_token VARCHAR(255),
  status VARCHAR(50) DEFAULT 'pending',
  total_cents INTEGER NOT NULL,
  currency VARCHAR(3) DEFAULT 'USD',
  payment_intent_id VARCHAR(255),
  shipping_address JSONB,
  billing_address JSONB,
  items JSONB NOT NULL,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Order Items
CREATE TABLE order_items (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
  product_id UUID REFERENCES products(id),
  quantity INTEGER NOT NULL,
  price_cents INTEGER NOT NULL,
  product_snapshot JSONB
);

-- Cart (for authenticated users)
CREATE TABLE cart_items (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id VARCHAR(255) NOT NULL,
  product_id UUID REFERENCES products(id) ON DELETE CASCADE,
  quantity INTEGER NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_products_slug ON products(slug);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_guest_token ON orders(guest_token);
CREATE INDEX idx_cart_user_id ON cart_items(user_id);

🎨 Step 3: Product Catalog API (20 min)

Create src/routes/products.ts:

typescript
import { Hono } from 'hono'
import { db } from '../db'

const products = new Hono()

// Get all products
products.get('/', async (c) => {
  const category = c.req.query('category')
  const search = c.req.query('search')
  
  let query = db
    .selectFrom('products')
    .selectAll()
    .where('active', '=', true)
  
  if (category) {
    query = query.where('category', '=', category)
  }
  
  if (search) {
    query = query.where('name', 'ilike', `%${search}%`)
  }
  
  const products = await query
    .orderBy('created_at', 'desc')
    .execute()
  
  return c.json({ products })
})

// Get single product
products.get('/:slug', async (c) => {
  const slug = c.req.param('slug')
  
  const product = await db
    .selectFrom('products')
    .selectAll()
    .where('slug', '=', slug)
    .where('active', '=', true)
    .executeTakeFirst()
  
  if (!product) {
    return c.json({ error: 'Product not found' }, 404)
  }
  
  return c.json({ product })
})

export default products

🛒 Step 4: Shopping Cart & Checkout (30 min)

4.1 Cart API

typescript
// src/routes/cart.ts
import { Hono } from 'hono'
import { validateSession } from '../middleware/auth'

const cart = new Hono()

// Get cart
cart.get('/', validateSession, async (c) => {
  const user = c.get('user')

  const items = await db
    .selectFrom('cart_items')
    .innerJoin('products', 'products.id', 'cart_items.product_id')
    .selectAll()
    .where('cart_items.user_id', '=', user.id)
    .execute()

  const total = items.reduce((sum, item) =>
    sum + (item.price_cents * item.quantity), 0
  )

  return c.json({ items, total_cents: total })
})

// Add to cart
cart.post('/add', validateSession, async (c) => {
  const user = c.get('user')
  const { product_id, quantity } = await c.req.json()

  await db
    .insertInto('cart_items')
    .values({ user_id: user.id, product_id, quantity })
    .onConflict((oc) => oc
      .column('user_id')
      .column('product_id')
      .doUpdateSet({ quantity: (eb) => eb.ref('quantity') + quantity })
    )
    .execute()

  return c.json({ success: true })
})

4.2 Checkout with Bridge Payments

typescript
// src/routes/checkout.ts
import { Hono } from 'hono'
import { validateSession } from '../middleware/auth'

const checkout = new Hono()

checkout.post('/', validateSession, async (c) => {
  const user = c.get('user')
  const { shipping_address, billing_address } = await c.req.json()

  // Get cart items
  const items = await db
    .selectFrom('cart_items')
    .innerJoin('products', 'products.id', 'cart_items.product_id')
    .selectAll()
    .where('cart_items.user_id', '=', user.id)
    .execute()

  const total_cents = items.reduce((sum, item) =>
    sum + (item.price_cents * item.quantity), 0
  )

  // Create payment intent with Bridge Payments
  const paymentResponse = await fetch(`${process.env.PAYMENTS_URL}/intents`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Session-Id': c.req.header('X-Session-Id') || ''
    },
    body: JSON.stringify({
      total_cents,
      currency: 'USD',
      concept: 'Order Payment',
      metadata: { user_id: user.id }
    })
  })

  const { client_secret, intent_id } = await paymentResponse.json()

  // Create order
  const order = await db
    .insertInto('orders')
    .values({
      user_id: user.id,
      total_cents,
      payment_intent_id: intent_id,
      shipping_address,
      billing_address,
      items: JSON.stringify(items),
      status: 'pending'
    })
    .returningAll()
    .executeTakeFirst()

  return c.json({ order, client_secret })
})

🎨 Step 5: Next.js Frontend (40 min)

5.1 Product Listing Page

tsx
// app/products/page.tsx
import { PubflowProvider } from '@pubflow/nextjs'

export default async function ProductsPage() {
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/products`)
  const { products } = await res.json()

  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-8">
      {products.map((product: any) => (
        <div key={product.id} className="border rounded-lg p-4">
          <img
            src={product.images[0]}
            alt={product.name}
            className="w-full h-48 object-cover rounded mb-4"
          />
          <h3 className="font-bold text-lg">{product.name}</h3>
          <p className="text-gray-600 text-sm mb-4">{product.description}</p>
          <div className="flex justify-between items-center">
            <span className="text-xl font-bold">
              ${(product.price_cents / 100).toFixed(2)}
            </span>
            <button className="bg-blue-600 text-white px-4 py-2 rounded">
              Add to Cart
            </button>
          </div>
        </div>
      ))}
    </div>
  )
}

5.2 Checkout Page

tsx
// app/checkout/page.tsx
'use client'
import { useState } from 'react'
import { useAuth } from '@pubflow/react'

export default function CheckoutPage() {
  const { user } = useAuth()
  const [loading, setLoading] = useState(false)

  const handleCheckout = async () => {
    setLoading(true)

    const response = await fetch('/api/checkout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Session-Id': localStorage.getItem('pubflow_session_id') || ''
      },
      body: JSON.stringify({
        shipping_address: {
          street: '123 Main St',
          city: 'New York',
          state: 'NY',
          zip: '10001'
        }
      })
    })

    const { client_secret } = await response.json()

    // Redirect to Stripe checkout
    window.location.href = `https://checkout.stripe.com/${client_secret}`
  }

  return (
    <div className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Checkout</h1>

      {/* Cart summary */}
      {/* Shipping form */}
      {/* Payment button */}

      <button
        onClick={handleCheckout}
        disabled={loading}
        className="w-full bg-blue-600 text-white py-3 rounded"
      >
        {loading ? 'Processing...' : 'Complete Purchase'}
      </button>
    </div>
  )
}

🚀 Step 6: Deploy & Test

Deploy Backend

bash
railway up

Deploy Frontend

bash
vercel --prod

🎉 Result

You've built a complete e-commerce store with:

  • ✅ Product catalog with search
  • ✅ Shopping cart functionality
  • ✅ Secure checkout with Stripe
  • ✅ Order management
  • ✅ User authentication

📖 Next Steps

  • 📧 Email notifications - Order confirmations
  • 📦 Shipping integration - Real-time tracking
  • Product reviews - Customer feedback
  • 🎁 Discount codes - Promotional campaigns
  • 📊 Analytics - Sales dashboard