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…
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
Callbacks
Callbacks allow you to execute custom code before or after data operations. Use them to validate data, enforce business rules, or trigger side effects when objects are created,…
Routes
Routes allow you to create custom HTTP endpoints for webhooks, APIs, and form handlers. They enable your Nimbu site to respond to HTTP requests with custom server-side logic.