E-Commerce Platforms with FraiseQL

Complex product relationships composed efficiently. Serve web, mobile, and internal APIs from a single source of truth.

E-Commerce Complexity

Modern e-commerce requires serving:

  • Product catalog - Categories, variants, attributes, images
  • Inventory - Real-time stock levels, warehouse locations
  • Pricing - Dynamic pricing, discounts, promotions
  • Reviews & ratings - User-generated content with aggregations
  • Recommendations - Related products, often-bought-together
  • Mobile-optimized responses - Minimal payload, predictable performance

FraiseQL: Single Source of Truth

Complete Product View

Compose the entire product entity in one view:

CREATE VIEW v_product_complete AS
SELECT
  jsonb_build_object(
    'id', p.id::text,
    'name', p.name,
    'description', p.description,
    'category', jsonb_build_object(
      'id', c.id::text,
      'name', c.name
    ),
    'variants', (
      SELECT jsonb_agg(jsonb_build_object(
        'id', v.id::text,
        'sku', v.sku,
        'color', v.color,
        'size', v.size,
        'price', v.price,
        'stock', (SELECT quantity FROM tb_inventory WHERE variant_id = v.id LIMIT 1)
      ))
      FROM tb_variant v
      WHERE v.product_id = p.id
    ),
    'images', (
      SELECT jsonb_agg(jsonb_build_object(
        'url', i.url,
        'alt', i.alt_text
      ))
      FROM tb_image i
      WHERE i.product_id = p.id
      ORDER BY i.display_order
    ),
    'reviews', jsonb_build_object(
      'average_rating', (SELECT avg(rating) FROM tb_review WHERE product_id = p.id),
      'count', (SELECT count(*) FROM tb_review WHERE product_id = p.id),
      'items', (
        SELECT jsonb_agg(jsonb_build_object(
          'rating', r.rating,
          'title', r.title,
          'text', r.text,
          'author', r.author_name,
          'helpful_count', r.helpful_count
        ))
        FROM tb_review r
        WHERE r.product_id = p.id
        ORDER BY r.created_at DESC
        LIMIT 10
      )
    ),
    'related_products', (
      SELECT jsonb_agg(jsonb_build_object(
        'id', rp.id::text,
        'name', rp.name,
        'price', (SELECT min(price) FROM tb_variant WHERE product_id = rp.id)
      ))
      FROM tb_product_relation pr
      JOIN tb_product rp ON pr.related_product_id = rp.id
      WHERE pr.product_id = p.id
      LIMIT 5
    )
  ) AS data
FROM tb_product p
LEFT JOIN tb_category c ON p.category_id = c.id;

Inventory & Pricing

CREATE VIEW v_variant_with_inventory AS
SELECT
  jsonb_build_object(
    'variant_id', v.id::text,
    'sku', v.sku,
    'inventory', jsonb_build_object(
      'total', sum(i.quantity) FILTER (WHERE i.status = 'available'),
      'by_warehouse', (
        SELECT jsonb_object_agg(w.name, i.quantity)
        FROM tb_inventory i
        JOIN tb_warehouse w ON i.warehouse_id = w.id
        WHERE i.variant_id = v.id
      )
    ),
    'pricing', jsonb_build_object(
      'base_price', v.price,
      'discount', (
        SELECT CASE
          WHEN d.type = 'percentage' THEN v.price * (d.value / 100.0)
          ELSE d.value
        END
        FROM tb_discount d
        WHERE d.variant_id = v.id
          AND NOW() BETWEEN d.start_date AND d.end_date
        LIMIT 1
      ),
      'final_price', v.price - COALESCE((
        SELECT CASE
          WHEN d.type = 'percentage' THEN v.price * (d.value / 100.0)
          ELSE d.value
        END
        FROM tb_discount d
        WHERE d.variant_id = v.id
          AND NOW() BETWEEN d.start_date AND d.end_date
        LIMIT 1
      ), 0)
    )
  ) AS data
FROM tb_variant v
GROUP BY v.id, v.sku, v.price;

Python Types

@fraiseql.type
class Category:
  id: ID
  name: str

@fraiseql.type
class Variant:
  id: ID
  sku: str
  color: str
  size: str
  price: float
  stock: int

@fraiseql.type
class Image:
  url: str
  alt: str

@fraiseql.type
class Review:
  rating: int
  title: str
  text: str
  author: str
  helpful_count: int

@fraiseql.type
class Product:
  id: ID
  name: str
  description: str
  category: Category
  variants: list[Variant]
  images: list[Image]
  reviews: ReviewSummary

Real-World Example: Product Listing

Request: Show product with all details

Mobile app requests product page data (web/iOS/Android all use same API)

Single Query Response:

@fraiseql.query(sql_source="v_product_complete")
def product(product_id: ID) -> Product:
  pass

# Returns:
# {
#   "id": "...",
#   "name": "Wireless Headphones",
#   "variants": [
#     { "id": "...", "color": "black", "stock": 45 },
#     { "id": "...", "color": "white", "stock": 12 }
#   ],
#   "images": [{ "url": "...", "alt": "..." }],
#   "reviews": {
#     "average_rating": 4.5,
#     "count": 234,
#     "items": [...]
#   },
#   "related_products": [...]
# }

Result: One database query. Complete product data for all clients (web, mobile, internal dashboards).

Performance Advantages

Mobile-First

Minimal payload. All data in one response. Reduces network latency.

~50-100ms per request

Inventory Consistency

Real-time stock levels from the database. No cache misses.

100% accurate

Scalable Variants

Thousands of variants per product. Single query still fast.

Linear complexity

Personalization Ready

Add user preferences to views. Personalized catalogs per user.

Custom data per request

Advanced Patterns

Personalized Product Feed

CREATE VIEW v_personalized_products AS
SELECT
  jsonb_build_object(
    'product_id', p.id::text,
    'relevance_score', (
      SELECT count(*) FROM tb_user_view
      WHERE product_id = p.id
        AND user_id = current_setting('app.user_id')::uuid
    ),
    'product_data', jsonb_build_object(
      'id', p.id::text,
      'name', p.name,
      'price', min(v.price)
    )
  ) AS data
FROM tb_product p
JOIN tb_variant v ON p.id = v.product_id
GROUP BY p.id
ORDER BY relevance_score DESC;

Cross-Sell Recommendations

CREATE VIEW v_often_bought_together AS
SELECT
  p1.id::text AS product_id,
  jsonb_build_object(
    'product_id', p1.id::text,
    'recommendations', (
      SELECT jsonb_agg(jsonb_build_object(
        'id', p2.id::text,
        'name', p2.name,
        'frequently_bought_together_count', count(*)
      ))
      FROM tb_order_item oi1
      JOIN tb_order_item oi2 ON oi1.order_id = oi2.order_id
      JOIN tb_product p2 ON oi2.product_id = p2.id
      WHERE oi1.product_id = p1.id
        AND p2.id != p1.id
      GROUP BY p2.id
      ORDER BY count(*) DESC
      LIMIT 5
    )
  ) AS data
FROM tb_product p1;

Related Resources

📊 Analytics

Real-time product metrics and sales dashboards.

Read →

📱 For Developers

Building client-side apps with FraiseQL.

Read →

⚡ Performance

Scale to millions of products naturally.

Read →

Ready to Build Your Store?

Start building scalable e-commerce APIs with FraiseQL today.