Skip to main content

Working with Document Databases in Cloud Functions

CloudBase document database is a flexible, scalable NoSQL database that supports JSON document storage. In cloud functions, you can operate the database using the Node.js SDK or HTTP API, enabling data CRUD operations, aggregation queries, and other features.

Database Overview

Core Concepts

  • (Collection): Similar to a table in a relational database, used for storing documents.
  • (Document): The basic storage unit in a database, using JSON format.
  • (Data Model): Structured data definition, providing type safety and data validation
  • (Index): A data structure that improves query performance

Technical Advantages

  • Flexibility: No need to predefine table schema, supports dynamic fields
  • Scalability: Automatically scale, supports massive data storage
  • Consistency: Supports ACID transactions, ensures data consistency
  • Real-time: Supports real-time data monitoring and synchronization

Overview of Operation Methods

Collection Operations

Collection Operations provide a direct and flexible database access method, suitable for rapid development and prototype validation.

Initialize Database

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
// Initialize CloudBase
const app = tcb.init({
env: tcb.SYMBOL_CURRENT_ENV // Use the current environment
});

// Obtain a database instance
const db = app.database();
const _ = db.command; // Obtain the query command

// Your business logic
return { success: true };
};

Data Query

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Query a single record by document ID
const result = await db.collection('users')
.doc(event.userId)
.get();

if (result.data.length === 0) {
return {
success: false,
error: 'User does not exist'
};
}

const user = result.data[0];

return {
success: true,
user: {
id: user._id,
name: user.name,
email: user.email,
createdAt: user.createdAt
}
};
} catch (error) {
console.error('Failed to query user:', error);
return {
success: false,
error: error.message
};
}
};

Data Operations

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Create a single record
const newUser = {
name: event.name,
email: event.email,
phone: event.phone,
status: 'active',
createdAt: new Date(),
updatedAt: new Date(),
profile: {
avatar: event.avatar || '',
bio: event.bio || '',
preferences: {
language: 'zh-CN',
timezone: 'Asia/Shanghai'
}
}
};

const result = await db.collection('users').add(newUser);

return {
success: true,
userId: result.id,
message: 'User created successfully'
};
} catch (error) {
console.error('Failed to create user:', error);
return {
success: false,
error: error.message
};
}
};

Data Model Operations

Data models provide structured data access methods, supporting type validation, field constraints, and associated queries.

Initialize Data Model

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });

// Obtain data model instance
const models = app.models;

// Access specific data model
const userModel = models.user;
const orderModel = models.order;
const productModel = models.product;

// Your business logic
return { success: true };
};

Data Model Query

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const models = app.models;

try {
// Query single user record
const result = await models.user.get({
filter: {
where: {
_id: {
$eq: event.userId
}
}
},
select: {
$master: true // Return all fields
}
});

if (result.data.records.length === 0) {
return {
success: false,
error: 'User does not exist'
};
}

const user = result.data.records[0];

return {
success: true,
user: user
};
} catch (error) {
console.error('Failed to query user:', error);
return {
success: false,
error: error.message
};
}
};

Data Model Operations

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const models = app.models;

try {
// Create a new order
const orderData = {
orderNumber: `ORD${Date.now()}`,
userId: event.userId,
items: event.items,
totalAmount: event.totalAmount,
shippingAddress: event.shippingAddress,
status: 'pending',
paymentMethod: event.paymentMethod,
notes: event.notes || ''
};

const result = await models.order.create({
data: orderData
});

return {
success: true,
orderId: result.data._id,
orderNumber: orderData.orderNumber,
message: 'Order created successfully'
};
} catch (error) {
console.error('Order creation failed:', error);
return {
success: false,
error: error.message
};
}
};

Advanced Features

Transaction Processing

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Start the transaction.
const transaction = await db.startTransaction();

try {
const { fromUserId, toUserId, amount } = event;

// 1. Check the transferring user's balance
const fromUserResult = await transaction.collection('users')
.doc(fromUserId)
.get();

if (fromUserResult.data[0].balance < amount) {
throw new Error('Insufficient balance');
}

// 2. Deduct the transfer-out user balance
await transaction.collection('users')
.doc(fromUserId)
.update({
balance: db.command.inc(-amount),
updatedAt: new Date()
});

// 3. Increase the transfer-in user's balance
await transaction.collection('users')
.doc(toUserId)
.update({
balance: db.command.inc(amount),
updatedAt: new Date()
});

// 4. Log the transfer
await transaction.collection('transactions').add({
fromUserId,
toUserId,
amount,
type: 'transfer',
status: 'completed',
createdAt: new Date()
});

// Commit the transaction.
await transaction.commit();

return {
success: true,
message: 'Transfer succeeded'
};
} catch (error) {
// Roll back the transaction.
await transaction.rollback();
throw error;
}
} catch (error) {
console.error('Transfer failed:', error);
return {
success: false,
error: error.message
};
}
};

Aggregation Query

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Sales Data Statistics
const salesStats = await db.collection('orders')
.aggregate()
.match({
status: 'completed',
createdAt: {
$gte: new Date(event.startDate),
$lte: new Date(event.endDate)
}
})
.group({
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
totalOrders: { $sum: 1 },
totalRevenue: { $sum: '$totalAmount' },
averageOrderValue: { $avg: '$totalAmount' },
maxOrderValue: { $max: '$totalAmount' },
minOrderValue: { $min: '$totalAmount' }
})
.sort({
'_id.year': 1,
'_id.month': 1,
'_id.day': 1
})
.end();

// Product Sales Statistics
const productStats = await db.collection('orders')
.aggregate()
.match({
status: 'completed'
})
.unwind('$items')
.group({
_id: '$items.productId',
totalSold: { $sum: '$items.quantity' },
totalRevenue: { $sum: { $multiply: ['$items.quantity', '$items.price'] } },
orderCount: { $sum: 1 }
})
.lookup({
from: 'products',
localField: '_id',
foreignField: '_id',
as: 'product'
})
.unwind('$product')
.project({
productName: '$product.name',
category: '$product.category',
totalSold: 1,
totalRevenue: 1,
orderCount: 1,
averagePrice: { $divide: ['$totalRevenue', '$totalSold'] }
})
.sort({ totalRevenue: -1 })
.limit(10)
.end();

return {
success: true,
data: {
dailySales: salesStats.list,
topProducts: productStats.list
}
};
} catch (error) {
console.error('Aggregation query failed:', error);
return {
success: false,
error: error.message
};
}
};

Geographic Location Query

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();
const _ = db.command;

try {
const { longitude, latitude, radius = 5000 } = event; // Default 5 kilometers

// Query nearby stores
const nearbyStores = await db.collection('stores')
.where({
location: _.geoNear({
geometry: new db.Geo.Point(longitude, latitude),
maxDistance: radius,
minDistance: 0
}),
status: 'active'
})
.field({
name: true,
address: true,
phone: true,
location: true,
businessHours: true,
services: true
})
.get();

// Calculate distance and sort
const storesWithDistance = nearbyStores.data.map(store => {
const distance = calculateDistance(
latitude, longitude,
store.location.coordinates[1], store.location.coordinates[0]
);

return {
...store,
distance: Math.round(distance)
};
}).sort((a, b) => a.distance - b.distance);

// Query the delivery area within the specified region
const deliveryArea = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(longitude - 0.01, latitude - 0.01),
new db.Geo.Point(longitude + 0.01, latitude - 0.01),
new db.Geo.Point(longitude + 0.01, latitude + 0.01),
new db.Geo.Point(longitude - 0.01, latitude + 0.01),
new db.Geo.Point(longitude - 0.01, latitude - 0.01)
])
]);

const deliveryStores = await db.collection('stores')
.where({
deliveryArea: _.geoIntersects({
geometry: deliveryArea
}),
deliveryEnabled: true
})
.get();

return {
success: true,
data: {
nearbyStores: storesWithDistance,
deliveryAvailable: deliveryStores.data.length > 0,
deliveryStores: deliveryStores.data
}
};
} catch (error) {
console.error('Geolocation query failed:', error);
return {
success: false,
error: error.message
};
}
};

// Calculate the distance between two points (in meters)
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3; // Earth radius (meters)
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;

const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

return R * c;
}

Data Monitoring and Real-time Synchronization

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Listen for order status changes
const watcher = db.collection('orders')
.where({
userId: event.userId,
status: db.command.in(['pending', 'processing', 'shipped'])
})
.watch({
onChange: (snapshot) => {
console.log('Order status changes:', snapshot);

// Handle changing documents
snapshot.docChanges.forEach(change => {
const { doc, queueType } = change;

switch (queueType) {
case 'init':
console.log('Initialize order:', doc.data());
break;
case 'update':
console.log('Order update:', doc.data());
// Send status update notification
sendOrderStatusNotification(doc.data());
break;
case 'add':
console.log('New order:', doc.data());
break;
case 'remove':
console.log('Order deletion:', doc.data());
break;
}
});
},
onError: (error) => {
console.error('Listen error:', error);
}
});

// Stop listening before the cloud function terminates
// watcher.close();

return {
success: true,
message: 'Order listening has started'
};
} catch (error) {
console.error('Failed to set data listening:', error);
return {
success: false,
error: error.message
};
}
};

async function sendOrderStatusNotification(order) {
// Logic for sending order status update notifications
console.log(`Sending notification: Order ${order.orderNumber} status updated to ${order.status}`);
}

Performance Optimization Recommendations

1. Index Optimization

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Create a composite index
await db.collection('orders').createIndex({
keys: {
userId: 1,
status: 1,
createdAt: -1
},
options: {
name: 'user_status_time_index',
background: true
}
});

// Create a geolocation index
await db.collection('stores').createIndex({
keys: {
location: '2dsphere'
},
options: {
name: 'location_index'
}
});

// Create a text search index
await db.collection('products').createIndex({
keys: {
name: 'text',
description: 'text',
tags: 'text'
},
options: {
name: 'text_search_index',
weights: {
name: 10,
description: 5,
tags: 1
}
}
});

return {
success: true,
message: 'Index created successfully'
};
} catch (error) {
console.error('Failed to create index:', error);
return {
success: false,
error: error.message
};
}
};

2. Query Optimization

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
// Optimize queries: Use projection to reduce data transfer
const result = await db.collection('orders')
.where({
userId: event.userId,
status: 'completed'
})
.field({
// Return only the required fields
orderNumber: true,
totalAmount: true,
createdAt: true,
// Exclude large fields
items: false,
shippingAddress: false
})
.orderBy('createdAt', 'desc')
.limit(20)
.get();

// Use aggregation pipeline to optimize complex queries
const stats = await db.collection('orders')
.aggregate()
.match({
userId: event.userId,
createdAt: {
$gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
}
})
.group({
_id: null,
totalOrders: { $sum: 1 },
totalAmount: { $sum: '$totalAmount' },
averageAmount: { $avg: '$totalAmount' }
})
.end();

return {
success: true,
orders: result.data,
statistics: stats.list[0] || {
totalOrders: 0,
totalAmount: 0,
averageAmount: 0
}
};
} catch (error) {
console.error('Query optimization failed:', error);
return {
success: false,
error: error.message
};
}
};

3. Connection Pool Management

const tcb = require('@cloudbase/node-sdk');

// Global initialization, reuse connections
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

exports.main = async (event, context) => {
try {
// Use a global database instance to avoid repeated initialization
const result = await db.collection('users')
.doc(event.userId)
.get();

return {
success: true,
user: result.data[0]
};
} catch (error) {
console.error('Database operation failed:', error);
return {
success: false,
error: error.message
};
}
};

Error Handling and Debugging

Common Error Handling

const tcb = require('@cloudbase/node-sdk');

exports.main = async (event, context) => {
const app = tcb.init({ env: tcb.SYMBOL_CURRENT_ENV });
const db = app.database();

try {
const result = await db.collection('users')
.doc(event.userId)
.get();

return {
success: true,
user: result.data[0]
};
} catch (error) {
console.error('Database operation failed:', {
error: error.message,
code: error.code,
requestId: error.requestId,
stack: error.stack
});

// Return different responses based on error types
switch (error.code) {
case 'PERMISSION_DENIED':
return {
success: false,
error: 'Insufficient permissions',
code: 'PERMISSION_DENIED'
};
case 'INVALID_PARAM':
return {
success: false,
error: 'Invalid parameter',
code: 'INVALID_PARAM'
};
case 'NETWORK_ERROR':
return {
success: false,
error: 'Network connection failed, please try again later',
code: 'NETWORK_ERROR'
};
default:
return {
success: false,
error: 'Database operation failed',
code: error.code || 'UNKNOWN_ERROR'
};
}
}
};