Cloud Functions
Cloud Functions let you define custom server-side logic that can be called from your themes, mobile apps, or external services. They provide a secure way to execute business logic on the server without exposing sensitive data or API keys.
What are Cloud Functions?
Cloud Functions are reusable server-side functions that:
- Run on Nimbu's servers with full access to the Nimbu SDK
- Can be called from themes using
Nimbu.Cloud.run() - Can be invoked via REST API
- Keep sensitive logic and credentials secure on the server
- Return JSON responses
Defining Functions
Use Nimbu.Cloud.define() to create a function:
Nimbu.Cloud.define('functionName', (req, res) => {
// Your logic here
res.success(result);
});Function Names
- Use descriptive names:
calculateShipping,validateCoupon,getRecommendations - Use camelCase for consistency
- Names must be unique within your Cloud Code
Request Object
The request object provides access to function parameters and context:
| Property | Description | Example |
|---|---|---|
req.params | Parameters passed to the function | req.params.productId |
req.meta | Metadata about the request | Request context info |
req.customer | Current authenticated customer (if any) | req.customer.get('email') |
Accessing Parameters
Nimbu.Cloud.define('calculateTotal', (req, res) => {
const { items, discount } = req.params;
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
const total = subtotal - discount;
res.success({ total });
});Response Object
Use the response object to return results or errors:
Success Response
Return a successful result:
res.success(resultData);The resultData can be any JSON-serializable value:
// Return an object
res.success({ total: 99.99, currency: 'EUR' });
// Return an array
res.success([{ id: 1, name: 'Product 1' }]);
// Return a simple value
res.success(42);
res.success('Hello');
res.success(true);Error Response
Return an error:
// Simple error message
res.error('Something went wrong');
// Error with HTTP status code
res.error(404, 'Product not found');
res.error(400, 'Invalid parameters');Common HTTP status codes:
400- Bad Request (invalid input)401- Unauthorized (authentication required)403- Forbidden (insufficient permissions)404- Not Found (resource doesn't exist)500- Internal Server Error (unexpected error)
Calling Functions
From Themes (Liquid)
Call Cloud Functions from your theme using the Nimbu JavaScript SDK:
<script src="{{ 'nimbu-js-sdk' | asset_url }}"></script>
<script>
Nimbu.initialize('{{ site.access_token }}');
// Call the function
Nimbu.Cloud.run('calculateShipping', {
weight: 2.5,
country: 'BE'
}).then(result => {
console.log('Shipping cost:', result.cost);
}).catch(error => {
console.error('Error:', error.message);
});
</script>From Other Cloud Code
Call functions from routes, callbacks, or jobs:
// Inside a route
Nimbu.Cloud.post('/checkout', async (req, res) => {
const shipping = await Nimbu.Cloud.run('calculateShipping', {
weight: req.params.weight,
country: req.params.country
});
res.json({ shippingCost: shipping.cost });
});Via REST API
Call functions using the REST API:
POST /sites/{site_id}/cloud/functions/{function_name}
Content-Type: application/json
{
"params": {
"productId": "123",
"quantity": 2
}
}Real-World Examples
Example 1: Calculate Shipping Cost
Nimbu.Cloud.define('calculateShipping', (req, res) => {
const { weight, country } = req.params;
if (!weight || !country) {
res.error(400, 'Weight and country are required');
return;
}
// Base rate per kg
let rate = weight * 0.5;
// Add surcharge for international shipping
if (country !== 'BE') {
rate += 5;
}
// Add extra for heavy packages
if (weight > 10) {
rate += 10;
}
res.success({
cost: Math.round(rate * 100) / 100,
currency: 'EUR',
estimatedDays: country === 'BE' ? 2 : 5
});
});Example 2: Validate Coupon Code
Nimbu.Cloud.define('validateCoupon', async (req, res) => {
const { code, cartTotal } = req.params;
if (!code) {
res.error(400, 'Coupon code is required');
return;
}
// Find the coupon
const query = new Nimbu.Query('coupons');
query.equalTo('code', code.toUpperCase());
const coupon = await query.first();
if (!coupon) {
res.error(404, 'Invalid coupon code');
return;
}
// Check if coupon is active
const now = new Date();
const validFrom = coupon.get('valid_from');
const validUntil = coupon.get('valid_until');
if (validFrom && now < new Date(validFrom)) {
res.error('Coupon is not yet valid');
return;
}
if (validUntil && now > new Date(validUntil)) {
res.error('Coupon has expired');
return;
}
// Check minimum order value
const minValue = coupon.get('minimum_order_value') || 0;
if (cartTotal < minValue) {
res.error(`Minimum order value is €${minValue}`);
return;
}
// Calculate discount
const discountType = coupon.get('discount_type');
const discountValue = coupon.get('discount_value');
let discount = 0;
if (discountType === 'percentage') {
discount = cartTotal * (discountValue / 100);
} else {
discount = discountValue;
}
res.success({
valid: true,
discount: Math.round(discount * 100) / 100,
code: coupon.get('code')
});
});Example 3: Get Product Recommendations
Nimbu.Cloud.define('getRecommendations', async (req, res) => {
const { productId, limit = 5 } = req.params;
if (!productId) {
res.error(400, 'Product ID is required');
return;
}
// Get the current product
const product = await new Nimbu.Query('products').get(productId);
if (!product) {
res.error(404, 'Product not found');
return;
}
// Find similar products in same category
const category = product.get('category');
const query = new Nimbu.Query('products');
query.equalTo('category', category);
query.equalTo('active', true);
query.notEqualTo('id', productId);
query.limit(limit);
const recommendations = await query.collection().fetch();
res.success({
product: {
id: product.id,
name: product.get('name')
},
recommendations: recommendations.map(p => ({
id: p.id,
name: p.get('name'),
price: p.get('price'),
image: p.get('image_url')
}))
});
});Example 4: Check Stock Availability
const HTTP = require('http');
Nimbu.Cloud.define('checkStock', async (req, res) => {
const { sku } = req.params;
if (!sku) {
res.error(400, 'SKU is required');
return;
}
try {
// Call external inventory API
const apiKey = Nimbu.Site.env.get('INVENTORY_API_KEY');
const response = await HTTP.get(`https://api.supplier.com/stock/${sku}`, {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const data = JSON.parse(response.body);
res.success({
sku: sku,
inStock: data.quantity > 0,
quantity: data.quantity,
nextDelivery: data.nextDelivery
});
} catch (error) {
console.error('Stock check failed:', error.message);
res.error(500, 'Unable to check stock availability');
}
});Example 5: Format Price with Locale
Nimbu.Cloud.define('formatPrice', (req, res) => {
const { amount, currency = 'EUR', locale = 'nl-BE' } = req.params;
if (amount === undefined) {
res.error(400, 'Amount is required');
return;
}
try {
const formatted = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
res.success({ formatted });
} catch (error) {
res.error(400, 'Invalid locale or currency');
}
});Example 6: Customer Loyalty Points
Nimbu.Cloud.define('addLoyaltyPoints', async (req, res) => {
const { points, reason } = req.params;
// Check if customer is authenticated
if (!req.customer) {
res.error(401, 'Authentication required');
return;
}
if (!points || points <= 0) {
res.error(400, 'Invalid points amount');
return;
}
// Add points to customer
const currentPoints = req.customer.get('loyalty_points') || 0;
req.customer.set('loyalty_points', currentPoints + points);
await req.customer.save();
// Log the transaction
const transaction = new Nimbu.Object('loyalty_transactions');
transaction.set('customer', req.customer);
transaction.set('points', points);
transaction.set('reason', reason || 'Manual addition');
transaction.set('date', new Date());
await transaction.save();
res.success({
pointsAdded: points,
totalPoints: currentPoints + points
});
});Working with Authenticated Users
Access the current customer in functions:
Nimbu.Cloud.define('getProfile', (req, res) => {
if (!req.customer) {
res.error(401, 'Please log in');
return;
}
res.success({
name: req.customer.get('name'),
email: req.customer.get('email'),
loyaltyPoints: req.customer.get('loyalty_points')
});
});Async Operations
Use async/await for asynchronous operations:
Nimbu.Cloud.define('processOrder', async (req, res) => {
const { orderId } = req.params;
try {
// Fetch order
const order = await new Nimbu.Query('orders').get(orderId);
// Process payment
await processPayment(order);
// Update inventory
await updateInventory(order);
// Send confirmation email
await sendConfirmation(order);
res.success({ status: 'processed' });
} catch (error) {
console.error('Order processing failed:', error);
res.error(500, 'Order processing failed');
}
});Error Handling
Always handle errors gracefully:
Nimbu.Cloud.define('externalApiCall', async (req, res) => {
try {
const result = await HTTP.get('https://api.example.com/data');
res.success(JSON.parse(result.body));
} catch (error) {
console.error('API call failed:', error.message);
// Return user-friendly error
res.error(500, 'Unable to fetch data. Please try again later.');
}
});Best Practices
1. Validate Input Parameters
Nimbu.Cloud.define('calculateDiscount', (req, res) => {
const { price, percentage } = req.params;
// Validate required parameters
if (price === undefined || percentage === undefined) {
res.error(400, 'Price and percentage are required');
return;
}
// Validate value ranges
if (price < 0) {
res.error(400, 'Price must be positive');
return;
}
if (percentage < 0 || percentage > 100) {
res.error(400, 'Percentage must be between 0 and 100');
return;
}
const discount = price * (percentage / 100);
res.success({ discount });
});2. Keep Functions Focused
// Good: Single responsibility
Nimbu.Cloud.define('calculateTax', (req, res) => {
const { amount, country } = req.params;
const tax = calculateTaxForCountry(amount, country);
res.success({ tax });
});
// Avoid: Doing too much
Nimbu.Cloud.define('processEverything', async (req, res) => {
// Calculates tax, shipping, processes payment, sends emails...
// Split into multiple focused functions instead
});3. Use Appropriate Status Codes
Nimbu.Cloud.define('getProduct', async (req, res) => {
const { id } = req.params;
const product = await new Nimbu.Query('products').get(id);
if (!product) {
res.error(404, 'Product not found'); // Use 404 for not found
return;
}
res.success(product);
});4. Log Important Operations
Nimbu.Cloud.define('refundOrder', async (req, res) => {
const { orderId, amount } = req.params;
console.log(`[Refund] Processing refund for order ${orderId}, amount: ${amount}`);
try {
await processRefund(orderId, amount);
console.log(`[Refund] Success for order ${orderId}`);
res.success({ status: 'refunded' });
} catch (error) {
console.error(`[Refund] Failed for order ${orderId}:`, error.message);
res.error(500, 'Refund processing failed');
}
});5. Keep Secrets Secure
const SiteVariables = require('site_variables');
Nimbu.Cloud.define('sendToExternalAPI', async (req, res) => {
// Good: Use site variables for secrets
const apiKey = SiteVariables.get('EXTERNAL_API_KEY');
// Avoid: Hard-coded secrets
// const apiKey = 'sk_live_abc123...'; // Never do this!
const result = await HTTP.post('https://api.example.com/endpoint', {
headers: { 'Authorization': `Bearer ${apiKey}` },
body: JSON.stringify(req.params)
});
res.success(JSON.parse(result.body));
});Common Patterns
Pagination
Nimbu.Cloud.define('listProducts', async (req, res) => {
const { page = 1, limit = 20 } = req.params;
const query = new Nimbu.Query('products');
query.equalTo('active', true);
query.limit(limit);
query.skip((page - 1) * limit);
const products = await query.collection().fetch();
const total = await query.count();
res.success({
products: products,
pagination: {
page: page,
limit: limit,
total: total,
pages: Math.ceil(total / limit)
}
});
});Conditional Logic
Nimbu.Cloud.define('getPrice', (req, res) => {
const { productId, customerType } = req.params;
let discount = 0;
if (customerType === 'wholesale') {
discount = 0.20; // 20% for wholesale
} else if (customerType === 'vip') {
discount = 0.10; // 10% for VIP
}
const basePrice = getProductPrice(productId);
const finalPrice = basePrice * (1 - discount);
res.success({ price: finalPrice, discount: discount });
});Testing Functions
From the Admin Interface
Test functions directly from the Cloud Code logs interface in the admin.
From Your Theme
<script>
// Test your function
Nimbu.Cloud.run('calculateShipping', {
weight: 2.5,
country: 'BE'
})
.then(result => console.log('Result:', result))
.catch(error => console.error('Error:', error));
</script>Using curl
curl -X POST https://yoursite.nimbu.io/api/cloud/functions/calculateShipping \
-H "Content-Type: application/json" \
-d '{"params": {"weight": 2.5, "country": "BE"}}'Next Steps
- Learn about Routes - Create custom HTTP endpoints
- Explore Callbacks - React to data changes
- Review Background Jobs - Schedule automated tasks
- Check Extensions - Add custom admin actions