Skip to content

Building an E-commerce API with Centrali

This guide shows you how to build a complete e-commerce backend with products, orders, inventory management, and payment processing.

Overview

We'll build: - Product catalog with categories - Shopping cart functionality - Order processing workflow - Inventory management - Payment integration - Customer accounts - Order notifications

Step 1: Define Your Data Models

Product Structure

{
  "name": "Product",
  "fields": {
    "name": {
      "type": "text",
      "required": true
    },
    "slug": {
      "type": "text",
      "unique": true,
      "required": true
    },
    "description": {
      "type": "longtext"
    },
    "price": {
      "type": "number",
      "required": true,
      "min": 0
    },
    "compareAtPrice": {
      "type": "number",
      "min": 0
    },
    "sku": {
      "type": "text",
      "unique": true,
      "required": true
    },
    "inventory": {
      "type": "number",
      "default": 0,
      "min": 0
    },
    "category": {
      "type": "select",
      "options": ["electronics", "clothing", "home", "books", "toys"],
      "required": true
    },
    "images": {
      "type": "array",
      "items": {
        "type": "url"
      }
    },
    "featured": {
      "type": "boolean",
      "default": false
    },
    "active": {
      "type": "boolean",
      "default": true
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "text"
      }
    },
    "weight": {
      "type": "number",
      "description": "Weight in grams"
    },
    "dimensions": {
      "type": "json",
      "schema": {
        "length": "number",
        "width": "number",
        "height": "number"
      }
    }
  }
}

Order Structure

{
  "name": "Order",
  "fields": {
    "orderNumber": {
      "type": "text",
      "unique": true,
      "required": true
    },
    "customerId": {
      "type": "reference",
      "structure": "Customer",
      "required": true
    },
    "status": {
      "type": "select",
      "options": ["pending", "processing", "shipped", "delivered", "cancelled", "refunded"],
      "default": "pending"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "json",
        "schema": {
          "productId": "text",
          "name": "text",
          "price": "number",
          "quantity": "number",
          "total": "number"
        }
      }
    },
    "subtotal": {
      "type": "number",
      "required": true
    },
    "tax": {
      "type": "number",
      "default": 0
    },
    "shipping": {
      "type": "number",
      "default": 0
    },
    "total": {
      "type": "number",
      "required": true
    },
    "shippingAddress": {
      "type": "json",
      "required": true
    },
    "billingAddress": {
      "type": "json"
    },
    "paymentMethod": {
      "type": "text"
    },
    "paymentId": {
      "type": "text"
    },
    "notes": {
      "type": "text"
    },
    "trackingNumber": {
      "type": "text"
    }
  }
}

Customer Structure

{
  "name": "Customer",
  "fields": {
    "email": {
      "type": "email",
      "required": true,
      "unique": true
    },
    "firstName": {
      "type": "text",
      "required": true
    },
    "lastName": {
      "type": "text",
      "required": true
    },
    "phone": {
      "type": "text"
    },
    "addresses": {
      "type": "array",
      "items": {
        "type": "json"
      }
    },
    "defaultShippingAddress": {
      "type": "number"
    },
    "defaultBillingAddress": {
      "type": "number"
    },
    "totalOrders": {
      "type": "number",
      "default": 0
    },
    "totalSpent": {
      "type": "number",
      "default": 0
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "text"
      }
    },
    "acceptsMarketing": {
      "type": "boolean",
      "default": false
    }
  }
}

Step 2: Implement Business Logic

Calculate Order Totals

// Function: calculateOrderTotals
exports.handler = async (event, context) => {
  const { centrali } = context.apis;
  const { items, customerId, shippingAddress } = event.data;

  // Calculate subtotal
  let subtotal = 0;
  const processedItems = [];

  for (const item of items) {
    const product = await centrali.records.get(item.productId);

    // Check inventory
    if (product.data.inventory < item.quantity) {
      return {
        success: false,
        error: `Insufficient inventory for ${product.data.name}`
      };
    }

    const itemTotal = product.data.price * item.quantity;
    subtotal += itemTotal;

    processedItems.push({
      productId: item.productId,
      name: product.data.name,
      price: product.data.price,
      quantity: item.quantity,
      total: itemTotal
    });
  }

  // Calculate tax (example: 10%)
  const tax = subtotal * 0.1;

  // Calculate shipping
  const shipping = calculateShipping(processedItems, shippingAddress);

  // Total
  const total = subtotal + tax + shipping;

  return {
    success: true,
    data: {
      items: processedItems,
      subtotal,
      tax,
      shipping,
      total
    }
  };
};

function calculateShipping(items, address) {
  // Simple shipping calculation
  const totalWeight = items.reduce((sum, item) => sum + (item.weight || 100) * item.quantity, 0);

  if (totalWeight < 1000) return 5.99;
  if (totalWeight < 5000) return 9.99;
  return 14.99;
}

Process Payment

// Function: processPayment
exports.handler = async (event, context) => {
  const { centrali, http } = context.apis;
  const { orderId, paymentMethod, paymentToken } = event.data;

  // Get order details
  const order = await centrali.records.get(orderId);

  // Process payment with Stripe
  try {
    const payment = await http.post('https://api.stripe.com/v1/charges', {
      headers: {
        'Authorization': `Bearer ${context.environment.STRIPE_SECRET_KEY}`,
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        amount: Math.round(order.data.total * 100), // Convert to cents
        currency: 'usd',
        source: paymentToken,
        description: `Order ${order.data.orderNumber}`,
        metadata: {
          orderId: order.id,
          customerEmail: order.data.customer.email
        }
      }).toString()
    });

    // Update order with payment details
    await centrali.records.update(orderId, {
      paymentId: payment.body.id,
      paymentMethod: paymentMethod,
      status: 'processing'
    });

    // Update inventory
    for (const item of order.data.items) {
      const product = await centrali.records.get(item.productId);
      await centrali.records.update(item.productId, {
        inventory: product.data.inventory - item.quantity
      });
    }

    // Send confirmation email
    await centrali.notifications.email({
      to: order.data.customer.email,
      template: 'order-confirmation',
      data: {
        orderNumber: order.data.orderNumber,
        items: order.data.items,
        total: order.data.total
      }
    });

    return {
      success: true,
      data: {
        paymentId: payment.body.id,
        status: 'success'
      }
    };
  } catch (error) {
    // Payment failed
    await centrali.records.update(orderId, {
      status: 'payment_failed',
      notes: error.message
    });

    return {
      success: false,
      error: 'Payment processing failed'
    };
  }
};

Manage Inventory

// Function: checkInventory
exports.handler = async (event, context) => {
  const { centrali } = context.apis;

  // Get low stock products
  const lowStock = await centrali.records.query({
    structure: 'Product',
    filter: { inventory: { $lt: 10 }, active: true }
  });

  // Send alert if products are low
  if (lowStock.length > 0) {
    const report = lowStock.map(p => ({
      name: p.data.name,
      sku: p.data.sku,
      inventory: p.data.inventory
    }));

    await centrali.notifications.email({
      to: 'inventory@store.com',
      subject: 'Low Stock Alert',
      template: 'low-stock',
      data: { products: report }
    });
  }

  return {
    success: true,
    data: {
      lowStockCount: lowStock.length,
      products: lowStock
    }
  };
};

Step 3: Set Up Automation

Order Processing Trigger

// Trigger: When order status changes to "processing"
{
  "name": "ProcessOrder",
  "type": "record",
  "config": {
    "structureId": "str_order",
    "event": "afterUpdate",
    "condition": "data.status === 'processing' && previous.status === 'pending'"
  },
  "functionId": "fn_fulfillOrder"
}

Abandoned Cart Recovery

// Function: recoverAbandonedCarts
exports.handler = async (event, context) => {
  const { centrali } = context.apis;

  // Find carts abandoned for more than 24 hours
  const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000);

  const abandonedCarts = await centrali.records.query({
    structure: 'Cart',
    filter: {
      status: 'active',
      updatedAt: { $lt: cutoff.toISOString() }
    }
  });

  for (const cart of abandonedCarts) {
    const customer = await centrali.records.get(cart.data.customerId);

    // Send recovery email
    await centrali.notifications.email({
      to: customer.data.email,
      template: 'abandoned-cart',
      data: {
        customerName: customer.data.firstName,
        cartItems: cart.data.items,
        cartTotal: cart.data.total,
        recoveryUrl: `https://store.com/cart/recover/${cart.id}`
      }
    });

    // Update cart status
    await centrali.records.update(cart.id, {
      status: 'recovery_sent'
    });
  }

  return {
    success: true,
    data: {
      cartsRecovered: abandonedCarts.length
    }
  };
};

// Schedule this to run daily
{
  "name": "DailyCartRecovery",
  "type": "schedule",
  "config": {
    "expression": "0 10 * * *"  // 10 AM daily
  },
  "functionId": "fn_recoverAbandonedCarts"
}

Step 4: API Endpoints

// Query products with filters
const searchProducts = async (query, category, minPrice, maxPrice) => {
  const response = await fetch('https://api.centrali.io/api/v1/query', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query: `
        FROM Product
        WHERE active = true
        ${category ? `AND category = "${category}"` : ''}
        ${minPrice ? `AND price >= ${minPrice}` : ''}
        ${maxPrice ? `AND price <= ${maxPrice}` : ''}
        ${query ? `AND SEARCH("${query}") IN (name, description)` : ''}
        ORDER BY featured DESC, createdAt DESC
        LIMIT 20
      `
    })
  });

  return response.json();
};

Shopping Cart Operations

class ShoppingCart {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.centrali.io/api/v1';
  }

  async addItem(cartId, productId, quantity) {
    // Get current cart
    const cart = await this.getCart(cartId);

    // Check if item exists
    const existingItem = cart.items.find(i => i.productId === productId);

    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      // Get product details
      const product = await this.getProduct(productId);

      cart.items.push({
        productId,
        name: product.data.name,
        price: product.data.price,
        quantity
      });
    }

    // Recalculate totals
    const totals = await this.calculateTotals(cart.items);

    // Update cart
    return await this.updateCart(cartId, {
      items: cart.items,
      ...totals
    });
  }

  async checkout(cartId, customerData, paymentToken) {
    const cart = await this.getCart(cartId);

    // Create order
    const order = await fetch(`${this.baseUrl}/records`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        structureId: 'str_order',
        data: {
          orderNumber: `ORD-${Date.now()}`,
          customerId: customerData.id,
          items: cart.items,
          subtotal: cart.subtotal,
          tax: cart.tax,
          shipping: cart.shipping,
          total: cart.total,
          shippingAddress: customerData.shippingAddress,
          billingAddress: customerData.billingAddress,
          status: 'pending'
        }
      })
    });

    const orderData = await order.json();

    // Process payment
    const payment = await this.processPayment(orderData.id, paymentToken);

    if (payment.success) {
      // Clear cart
      await this.clearCart(cartId);
    }

    return payment;
  }
}

Step 5: Frontend Integration

React Product Listing

function ProductList() {
  const [products, setProducts] = useState([]);
  const [filters, setFilters] = useState({
    category: '',
    minPrice: 0,
    maxPrice: 1000
  });

  useEffect(() => {
    fetchProducts();
  }, [filters]);

  const fetchProducts = async () => {
    const query = `
      FROM Product
      WHERE active = true
      ${filters.category ? `AND category = "${filters.category}"` : ''}
      AND price BETWEEN ${filters.minPrice} AND ${filters.maxPrice}
      ORDER BY featured DESC, createdAt DESC
    `;

    const response = await centrali.query(query);
    setProducts(response.data);
  };

  const addToCart = async (productId) => {
    await cart.addItem(productId, 1);
    toast.success('Added to cart!');
  };

  return (
    <div className="products">
      <div className="filters">
        <select onChange={(e) => setFilters({...filters, category: e.target.value})}>
          <option value="">All Categories</option>
          <option value="electronics">Electronics</option>
          <option value="clothing">Clothing</option>
          <option value="home">Home</option>
        </select>

        <input
          type="range"
          min="0"
          max="1000"
          value={filters.maxPrice}
          onChange={(e) => setFilters({...filters, maxPrice: e.target.value})}
        />
      </div>

      <div className="product-grid">
        {products.map(product => (
          <div key={product.id} className="product-card">
            <img src={product.data.images[0]} alt={product.data.name} />
            <h3>{product.data.name}</h3>
            <p className="price">
              ${product.data.price}
              {product.data.compareAtPrice && (
                <span className="original">${product.data.compareAtPrice}</span>
              )}
            </p>
            <p>{product.data.description}</p>
            <button onClick={() => addToCart(product.id)}>
              Add to Cart
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}

Checkout Flow

function Checkout() {
  const [cart, setCart] = useState(null);
  const [customer, setCustomer] = useState({
    email: '',
    firstName: '',
    lastName: '',
    shippingAddress: {}
  });

  const processCheckout = async (paymentToken) => {
    try {
      // Create order
      const order = await centrali.records.create({
        structure: 'Order',
        data: {
          orderNumber: generateOrderNumber(),
          customer,
          items: cart.items,
          total: cart.total,
          status: 'pending'
        }
      });

      // Process payment
      const payment = await centrali.functions.execute('processPayment', {
        orderId: order.id,
        paymentToken
      });

      if (payment.success) {
        router.push(`/order-confirmation/${order.id}`);
      }
    } catch (error) {
      console.error('Checkout failed:', error);
    }
  };

  return (
    <div className="checkout">
      <div className="order-summary">
        <h2>Order Summary</h2>
        {cart?.items.map(item => (
          <div key={item.productId}>
            {item.name} x {item.quantity} = ${item.total}
          </div>
        ))}
        <div className="total">
          Total: ${cart?.total}
        </div>
      </div>

      <CustomerForm
        customer={customer}
        onChange={setCustomer}
      />

      <PaymentForm
        amount={cart?.total}
        onSuccess={processCheckout}
      />
    </div>
  );
}

Step 6: Analytics & Reporting

Sales Dashboard

// Function: generateSalesReport
exports.handler = async (event, context) => {
  const { centrali } = context.apis;
  const { startDate, endDate } = event.data;

  // Get orders in date range
  const orders = await centrali.records.query({
    structure: 'Order',
    filter: {
      createdAt: { $gte: startDate, $lte: endDate },
      status: { $in: ['processing', 'shipped', 'delivered'] }
    }
  });

  // Calculate metrics
  const metrics = {
    totalOrders: orders.length,
    totalRevenue: orders.reduce((sum, o) => sum + o.data.total, 0),
    averageOrderValue: orders.length > 0 ?
      orders.reduce((sum, o) => sum + o.data.total, 0) / orders.length : 0,
    topProducts: await getTopProducts(orders),
    ordersByStatus: groupBy(orders, 'data.status'),
    revenueByDay: groupRevenueByDay(orders)
  };

  return {
    success: true,
    data: metrics
  };
};

Best Practices

1. Inventory Management

  • Always check inventory before confirming orders
  • Use atomic operations to prevent overselling
  • Set up low stock alerts

2. Payment Security

  • Never store credit card details
  • Use payment tokens from providers like Stripe
  • Always verify payment status before fulfilling

3. Order Workflow

  • Use clear status transitions
  • Send notifications at each stage
  • Implement rollback for failed payments

4. Performance

  • Cache product listings
  • Use pagination for large catalogs
  • Index frequently searched fields

5. Customer Experience

  • Send order confirmations immediately
  • Provide order tracking
  • Implement cart recovery emails

Summary

This e-commerce implementation shows how Centrali handles: - Complex data relationships - Payment processing - Inventory management - Order fulfillment - Customer communications - Analytics and reporting

The same patterns can be extended for more advanced features like subscriptions, multi-vendor marketplaces, or B2B commerce!