🛒 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
| Component | Technology | Purpose |
|---|---|---|
| 🔐 Flowless | Managed Service (pubflow.com) | Authentication - User accounts, guest checkout |
| ⚡ Flowfull | Node.js (flowfull-node) | Your custom backend - Products, orders, inventory APIs |
| 🎨 Flowfull Clients | Next.js (@pubflow/nextjs) | Your frontend - Storefront with SSR |
| 💳 Bridge Payments | Managed Service | Payment 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:
- Frontend (Next.js) → Your Backend (Flowfull/flowfull-node)
- Your Backend → Flowless for authentication
- Your Backend → Bridge Payments for payment processing
- 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
- Visit pubflow.com
- Sign up or log in
- Click "Create Flowless Instance"
- Name it:
store-auth - Enable guest accounts for guest checkout
- Configure OAuth (Google, Facebook) for social login
- 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
- Create Bridge Payments instance at pubflow.com
- Add Stripe credentials
- Add PayPal credentials (optional)
- Configure webhooks
- 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.
# 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 install2.2 Configure Environment Variables
Copy the example environment file:
cp .env.example .envEdit .env with your configuration:
# 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.1BRIDGE_VALIDATION_SECRET: Bridge secret from Pubflow dashboardSTRIPE_SECRET_KEY: Your Stripe secret key from Stripe DashboardBRIDGE_PAYMENTS_URL: Your Bridge Payments instance URL from Step 1.2
📊 Step 3: Database Schema (15 min)
Create schema.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:
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
// 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
// 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
// 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
// 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
railway upDeploy Frontend
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
📚 Related Resources
- 🔐 Flowless Documentation - Authentication guide
- ⚡ Flowfull Documentation - Backend framework
- 💳 Bridge Payments - Payment integration
- 💬 Discord Community - Get help