Skip to content

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:

js
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:

PropertyDescriptionExample
req.paramsParameters passed to the functionreq.params.productId
req.metaMetadata about the requestRequest context info
req.customerCurrent authenticated customer (if any)req.customer.get('email')

Accessing Parameters

js
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:

js
res.success(resultData);

The resultData can be any JSON-serializable value:

js
// 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:

js
// 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:

liquid
<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:

js
// 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:

bash
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

js
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

js
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

js
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

js
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

js
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

js
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:

js
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:

js
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:

js
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

js
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

js
// 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

js
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

js
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

js
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

js
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

js
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

liquid
<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

bash
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