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¶
Product Search¶
// 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!