Skip to content
On this page

🛒 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 IF NOT EXIST idx_products_category ON products(category);
CREATE INDEX IF NOT EXIST idx_products_slug ON products(slug);
CREATE INDEX IF NOT EXIST idx_orders_user_id ON orders(user_id);
CREATE INDEX IF NOT EXIST idx_orders_guest_token ON orders(guest_token);
CREATE INDEX IF NOT EXIST 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