
Cloudbase Document Database In Wechat Miniprogram
- 44 installs
- 1.1k repo stars
- Updated August 4, 2026
- tencentcloudbase/cloudbase-mcp
This is a copy of cloudbase-document-database-in-wechat-miniprogram by tencentcloudbase - installs and ranking accrue to the original listing.
Helps with databases tasks during AI-assisted development.
About
cloudbase-document-database-in-wechat-miniprogram is a Claude Code skill in the Databases category.
- cloudbase-document-database-in-wechat-miniprogram
- Databases
- AI-coding skill
Cloudbase Document Database In Wechat Miniprogram by the numbers
- 44 all-time installs (skills.sh)
- +2 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/tencentcloudbase/cloudbase-mcp --skill cloudbase-document-database-in-wechat-miniprogramAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 44 |
|---|---|
| repo stars | ★ 1.1k |
| Last updated | August 4, 2026 |
| Repository | tencentcloudbase/cloudbase-mcp ↗ |
What it does
Helps with databases tasks during AI-assisted development.
Files
Standalone Install Note
If this environment only installed the current skill, start from the CloudBase main entry and use the published cloudbase/references/... paths for sibling skills.
- CloudBase main entry:
https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/SKILL.md - Current skill raw source:
https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/no-sql-wx-mp-sdk/SKILL.md
Keep local references/... paths for files that ship with the current skill directory. When this file points to a sibling skill such as auth-tool or web-development, use the standalone fallback URL shown next to that reference.
CloudBase Document Database WeChat Mini Program SDK
Activation Contract
Use this first when
- A WeChat Mini Program must access CloudBase document database through
wx.cloud.database(). - The request mentions Mini Program collection CRUD, pagination, aggregation, or geolocation queries.
Read before writing code if
- The task is Mini Program database work but you still need to separate it from Web SDK, cloud functions, or SQL tasks.
- The request depends on built-in user identity,
_openid, or Mini Program-side permissions.
Then also read
- Mini Program project rules and CloudBase integration ->
../miniprogram-development/SKILL.md(standalone fallback:https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/miniprogram-development/SKILL.md) - Mini Program auth and identity flow ->
../auth-wechat/SKILL.md(standalone fallback:https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/auth-wechat/SKILL.md) - Browser-side document database code ->
../no-sql-web-sdk/SKILL.md(standalone fallback:https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/no-sql-web-sdk/SKILL.md)
Do NOT use for
- Browser/Web code using
@cloudbase/js-sdk. - Server-side or cloud-function database access.
- MySQL / relational database work.
Common mistakes / gotchas
- Copying Web SDK code into Mini Program pages.
- Manually writing
_openidduring create or update operations. - Assuming built-in Mini Program identity means security rules can be ignored.
- Mixing collection CRUD and backend-wide admin workflows in the same client path.
Minimal checklist
- Confirm the caller is a Mini Program page/component or Mini Program-side logic.
- Initialize
wx.cloudcorrectly before database calls. - Verify whether the collection rules rely on
auth.openid/_openid. - Read the specific companion reference file for the operation you need.
Overview
This skill covers Mini Program-side document database access through wx.cloud.database().
Use it for:
- collection CRUD in Mini Program pages
- query composition and pagination
- aggregation
- geolocation queries
Mini Program CloudBase access comes with built-in identity, but database operations are still constrained by collection permissions and security rules.
Canonical initialization
const db = wx.cloud.database();
const _ = db.command;To target a specific environment:
const db = wx.cloud.database({
env: "test"
});Important notes:
- Users are authenticated through the Mini Program CloudBase context.
- In cloud functions, caller identity is available through
wxContext.OPENID. - In client-side collection rules, ownership checks usually use
auth.openid/doc._openid.
Quick routing
- CRUD ->
./crud-operations.md - Complex queries ->
./complex-queries.md - Pagination ->
./pagination.md - Aggregation ->
./aggregation.md - Geolocation ->
./geolocation.md - Security rules ->
./security-rules.md
Working rules for a coding agent
1. Keep Mini Program code Mini Program-native
- Use
wx.cloud.database(). - Do not substitute browser SDK initialization patterns.
2. Respect ownership fields
_openidis system-managed for SDK writes.- Never set or override
_openidmanually in.add(),.set(), or.update()payloads.
3. Remember that security rules validate requests
- If a rule requires ownership conditions, the query shape must match that rule model.
- Permission errors usually mean the rule/query relationship is wrong, not only that the user is logged out.
4. Route admin-style operations to backend flows
- If the task needs privileged global access, use backend tools or functions instead of exposing that path directly in Mini Program client code.
Quick examples
Basic collection access
const todos = db.collection("todos");
const result = await todos.where({ completed: false }).get();Document reference
const todo = db.collection("todos").doc("todo-id");
const result = await todo.get();Best practices
1. Create clear collection naming conventions. 2. Use typed wrappers or model helpers in app code where possible. 3. Design rules around real ownership and sharing patterns. 4. Use pagination instead of large unbounded reads. 5. Keep admin/operations logic in backend code, not Mini Program direct access.
Aggregation Queries with CloudBase
This document explains how to perform aggregation operations for data analysis and statistics in CloudBase document database.
Overview
Aggregation queries allow you to:
- Group data by specific fields
- Calculate statistics (count, sum, average, etc.)
- Transform and reshape data
- Perform complex data analysis
Basic Aggregation Syntax
const result = await db.collection('collectionName')
.aggregate()
.group({ /* grouping configuration */ })
.end();
console.log('Results:', result.list);Note: Aggregation queries use .end() instead of .get()
Grouping Data
Simple Grouping with Count
Count documents by a specific field:
// Count todos by priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$priority', // Group by priority field
count: {
$sum: 1 // Count documents in each group
}
})
.end();
console.log('By priority:', result.list);
// Output: [
// { _id: 'high', count: 15 },
// { _id: 'medium', count: 23 },
// { _id: 'low', count: 8 }
// ]Field Reference Syntax
Use $ prefix to reference document fields:
$priority- References thepriorityfield$status- References thestatusfield$user.name- References nested fields
Aggregation Operators
Accumulator Operators
| Operator | Description | Usage |
|---|---|---|
$sum | Sum values | { total: { $sum: '$amount' } } |
$avg | Average values | { avgScore: { $avg: '$score' } } |
$min | Minimum value | { minPrice: { $min: '$price' } } |
$max | Maximum value | { maxPrice: { $max: '$price' } } |
$first | First value | { first: { $first: '$date' } } |
$last | Last value | { last: { $last: '$date' } } |
$push | Array of all values | { items: { $push: '$name' } } |
Common Aggregation Patterns
Count by Category
// Count users by role
const result = await db.collection('users')
.aggregate()
.group({
_id: '$role',
count: { $sum: 1 }
})
.end();Sum and Average
// Calculate total and average order amount by customer
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
totalAmount: { $sum: '$amount' },
averageAmount: { $avg: '$amount' },
orderCount: { $sum: 1 }
})
.end();Find Min and Max
// Find price range by product category
const result = await db.collection('products')
.aggregate()
.group({
_id: '$category',
minPrice: { $min: '$price' },
maxPrice: { $max: '$price' },
avgPrice: { $avg: '$price' }
})
.end();Multiple Groups
// Group by status and priority
const result = await db.collection('todos')
.aggregate()
.group({
_id: {
status: '$status',
priority: '$priority'
},
count: { $sum: 1 }
})
.end();
// Output: [
// { _id: { status: 'active', priority: 'high' }, count: 5 },
// { _id: { status: 'active', priority: 'low' }, count: 3 },
// { _id: { status: 'completed', priority: 'high' }, count: 10 }
// ]Pipeline Stages
Aggregation supports multiple stages in a pipeline:
Match Stage (Filter)
Filter documents before grouping:
const result = await db.collection('orders')
.aggregate()
.match({
status: 'completed',
createdAt: db.command.gte(new Date('2025-01-01'))
})
.group({
_id: '$customerId',
totalRevenue: { $sum: '$amount' }
})
.end();Sort Stage
Sort the aggregation results:
const result = await db.collection('todos')
.aggregate()
.group({
_id: '$assignee',
taskCount: { $sum: 1 }
})
.sort({
taskCount: -1 // -1 for descending, 1 for ascending
})
.end();Limit Stage
Limit the number of results:
// Top 10 customers by order count
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$customerId',
orderCount: { $sum: 1 }
})
.sort({ orderCount: -1 })
.limit(10)
.end();Project Stage
Reshape output documents:
const result = await db.collection('users')
.aggregate()
.group({
_id: '$department',
employeeCount: { $sum: 1 },
avgSalary: { $avg: '$salary' }
})
.project({
department: '$_id',
employees: '$employeeCount',
averageSalary: '$avgSalary',
_id: 0 // Exclude _id from output
})
.end();Complete Pipeline Example
// Comprehensive sales analysis
const salesAnalysis = await db.collection('orders')
.aggregate()
// Stage 1: Filter to completed orders in 2025
.match({
status: 'completed',
orderDate: db.command.gte(new Date('2025-01-01'))
})
// Stage 2: Group by product category
.group({
_id: '$category',
totalRevenue: { $sum: '$amount' },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: '$amount' },
maxOrder: { $max: '$amount' },
minOrder: { $min: '$amount' }
})
// Stage 3: Sort by revenue descending
.sort({
totalRevenue: -1
})
// Stage 4: Limit to top 5 categories
.limit(5)
// Stage 5: Reshape output
.project({
category: '$_id',
revenue: '$totalRevenue',
orders: '$orderCount',
averageValue: '$avgOrderValue',
range: {
min: '$minOrder',
max: '$maxOrder'
},
_id: 0
})
.end();
console.log('Top 5 categories:', salesAnalysis.list);Time-based Aggregations
Group by Date
// Count orders by date
const result = await db.collection('orders')
.aggregate()
.group({
_id: {
year: db.command.aggregate.dateToString({
format: '%Y',
date: '$createdAt'
}),
month: db.command.aggregate.dateToString({
format: '%m',
date: '$createdAt'
})
},
orderCount: { $sum: 1 },
revenue: { $sum: '$amount' }
})
.sort({
'_id.year': 1,
'_id.month': 1
})
.end();Array Aggregations
Working with Array Fields
// Unwind array fields for analysis
const result = await db.collection('orders')
.aggregate()
.unwind('$items') // Flatten items array
.group({
_id: '$items.productId',
totalQuantity: { $sum: '$items.quantity' },
totalRevenue: { $sum: '$items.total' }
})
.sort({ totalRevenue: -1 })
.limit(10)
.end();Performance Tips
1. Use match early: Filter data before grouping to reduce processing 2. Index match fields: Ensure fields used in match stage are indexed 3. Limit results: Use limit to reduce data transfer 4. Avoid large groups: Very large groups can impact performance 5. Project only needed fields: Remove unnecessary fields early
Common Use Cases
Dashboard Statistics
// Get overview statistics
const stats = await db.collection('todos')
.aggregate()
.group({
_id: null, // Single group for overall stats
total: { $sum: 1 },
completed: {
$sum: {
$cond: [{ $eq: ['$status', 'completed'] }, 1, 0]
}
},
active: {
$sum: {
$cond: [{ $eq: ['$status', 'active'] }, 1, 0]
}
}
})
.end();User Activity Analysis
// Analyze user activity
const userActivity = await db.collection('activities')
.aggregate()
.match({
timestamp: db.command.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.group({
_id: '$userId',
actionCount: { $sum: 1 },
lastAction: { $max: '$timestamp' },
actions: { $push: '$actionType' }
})
.sort({ actionCount: -1 })
.limit(20)
.end();Error Handling
Always handle aggregation errors:
try {
const result = await db.collection('orders')
.aggregate()
.group({
_id: '$category',
total: { $sum: '$amount' }
})
.end();
if (result.list.length === 0) {
console.log('No data found');
} else {
console.log('Aggregation results:', result.list);
}
} catch (error) {
console.error('Aggregation failed:', error);
}Complex Queries with CloudBase
This document provides detailed guidance on constructing complex queries using CloudBase document database.
Query Operators
Access operators through db.command:
const _ = db.command;Comparison Operators
| Operator | Usage | Description |
|---|---|---|
gt | _.gt(value) | Greater than |
gte | _.gte(value) | Greater than or equal |
lt | _.lt(value) | Less than |
lte | _.lte(value) | Less than or equal |
eq | _.eq(value) | Equal to |
neq | _.neq(value) | Not equal to |
Array Operators
| Operator | Usage | Description |
|---|---|---|
in | _.in([values]) | Value exists in array |
nin | _.nin([values]) | Value not in array |
Building Complex Queries
Multiple Conditions
Combine multiple conditions in the where() object:
const result = await db.collection('todos')
.where({
// Age greater than 18
age: _.gt(18),
// Tags include 'tech' or 'study'
tags: _.in(['tech', 'study']),
// Created within last week
createdAt: _.gte(new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
})
.get();Sorting Results
Use orderBy() to sort results:
// Single field sorting
db.collection('posts')
.orderBy('createdAt', 'desc')
.get()
// Multiple field sorting (chain multiple orderBy calls)
db.collection('products')
.orderBy('category', 'asc')
.orderBy('price', 'desc')
.get()Sort directions:
'asc'- Ascending order'desc'- Descending order
Limiting Results
Control the number of results returned:
// Limit to 10 results
db.collection('posts')
.limit(10)
.get()Limits:
- Default: 100 records
- Maximum: 1000 records per query
Field Selection
Optimize queries by selecting only needed fields:
const result = await db.collection('users')
.field({
title: true, // Include title
completed: true, // Include completed
createdAt: true, // Include createdAt
_id: false // Exclude _id
})
.get();Field selection rules:
true- Include field in resultsfalse- Exclude field from results- If not specified, all fields are included by default
Complete Complex Query Example
Here's a comprehensive example combining all query features:
const _ = db.command;
const result = await db.collection('todos')
.where({
// Status must be 'active' or 'pending'
status: _.in(['active', 'pending']),
// Priority is high
priority: 'high',
// Age greater than 18
age: _.gt(18),
// Created in the last 30 days
createdAt: _.gte(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
})
.field({
title: true,
status: true,
priority: true,
assignee: true,
createdAt: true
})
.orderBy('createdAt', 'desc')
.orderBy('priority', 'asc')
.limit(50)
.skip(0)
.get();
console.log('Found', result.data.length, 'todos');
console.log('Results:', result.data);Query Performance Tips
1. Use Indexes: Create indexes on frequently queried fields 2. Limit Fields: Only select fields you need with .field() 3. Apply Filters Early: Use specific where() conditions to reduce data scanned 4. Reasonable Limits: Don't query more data than necessary 5. Optimize Sort Fields: Sort on indexed fields when possible
Common Query Patterns
Date Range Queries
const startDate = new Date('2025-01-01');
const endDate = new Date('2025-12-31');
db.collection('events')
.where({
eventDate: _.gte(startDate).and(_.lte(endDate))
})
.get()Text Search (Exact Match)
// Exact title match
db.collection('articles')
.where({
title: 'Specific Title'
})
.get()Multiple Value Matching
// Find users with specific roles
db.collection('users')
.where({
role: _.in(['admin', 'moderator', 'editor'])
})
.get()Excluding Values
// Find posts not in draft or archived status
db.collection('posts')
.where({
status: _.nin(['draft', 'archived'])
})
.get()Combining with Logical Operators
// Users over 18 OR with verified status
db.collection('users')
.where({
_or: [
{ age: _.gt(18) },
{ verified: true }
]
})
.get()Error Handling
Always handle potential errors:
try {
const result = await db.collection('todos')
.where({ status: _.in(['active']) })
.orderBy('priority', 'desc')
.limit(10)
.get();
if (result.data.length === 0) {
console.log('No matching documents found');
} else {
console.log('Found documents:', result.data);
}
} catch (error) {
console.error('Query failed:', error);
// Handle error appropriately
}CRUD Operations with CloudBase
This document covers Create, Update, and Delete operations for CloudBase document database.
Create Operations
Adding a Single Document
Add a new document to a collection:
// Add a single document
const result = await db.collection('todos').add({
title: 'Learn CloudBase',
description: 'Study the database API',
completed: false,
priority: 'high',
createdAt: new Date()
});
console.log('Added document with ID:', result._id);Return Value:
{
_id: "generated-doc-id", // Auto-generated document ID
// ... other metadata
}Adding with Custom ID
Specify your own document ID:
// Add with custom ID
const result = await db.collection('todos')
.doc('custom-todo-id')
.set({
title: 'Custom ID Todo',
completed: false,
createdAt: new Date()
});Note: Use .set() with .doc() to specify a custom ID. If document exists, it will be overwritten.
Adding Multiple Documents
Add multiple documents at once:
// Batch add documents
const todos = [
{ title: 'Task 1', completed: false },
{ title: 'Task 2', completed: false },
{ title: 'Task 3', completed: true }
];
// Add one by one
for (const todo of todos) {
await db.collection('todos').add(todo);
}
// Or use Promise.all for parallel insertion
const results = await Promise.all(
todos.map(todo => db.collection('todos').add(todo))
);
console.log('Added', results.length, 'documents');Data Validation
Validate data before insertion:
function validateTodo(todo) {
if (!todo.title || todo.title.trim() === '') {
throw new Error('Title is required');
}
if (typeof todo.completed !== 'boolean') {
throw new Error('Completed must be a boolean');
}
return true;
}
async function addTodo(todoData) {
try {
validateTodo(todoData);
const result = await db.collection('todos').add({
...todoData,
createdAt: new Date(),
updatedAt: new Date()
});
return result;
} catch (error) {
console.error('Failed to add todo:', error);
throw error;
}
}Update Operations
Update by Document ID
Update a specific document by its ID:
// Update by ID
const result = await db.collection('todos')
.doc('todo-id-123')
.update({
completed: true,
updatedAt: new Date()
});
console.log('Updated:', result.updated, 'document(s)');Return Value:
{
updated: 1, // Number of documents updated
// ... other metadata
}Update with Conditions
Update documents matching specific conditions:
// Update all incomplete high-priority todos
const result = await db.collection('todos')
.where({
completed: false,
priority: 'high'
})
.update({
priority: 'urgent',
updatedAt: new Date()
});
console.log('Updated', result.updated, 'documents');Partial Updates
Only update specific fields (other fields remain unchanged):
// Only update the title, leave other fields unchanged
await db.collection('todos')
.doc('todo-id-123')
.update({
title: 'Updated Title'
});Nested Field Updates (Important)
When updating nested object fields, you must use dot notation if you want to preserve sibling fields.
WRONG: This replaces the entire object and deletes sibling fields:
// DANGER: If 'user' had an 'email' field, it is now deleted!
await db.collection('profiles')
.doc('profile-123')
.update({
user: {
name: 'New Name' // Replaces the ENTIRE 'user' object
}
});CORRECT: This only updates the specific nested field:
// SAFE: Only updates 'name', preserves 'email' and other fields in 'user'
await db.collection('profiles')
.doc('profile-123')
.update({
'user.name': 'New Name' // Use dot notation for nested fields
});Update with Operators
Use update operators for complex updates:
const _ = db.command;
// Increment a counter
await db.collection('posts')
.doc('post-123')
.update({
views: _.inc(1) // Increment views by 1
});
// Add item to array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.push(['urgent']) // Add 'urgent' to tags array
});
// Remove item from array
await db.collection('todos')
.doc('todo-123')
.update({
tags: _.pull('completed') // Remove 'completed' from tags
});
// Multiply a number
await db.collection('products')
.doc('product-123')
.update({
price: _.mul(1.1) // Increase price by 10%
});Common Update Operators
| Operator | Description | Example |
|---|---|---|
_.inc(n) | Increment by n | views: _.inc(1) |
_.mul(n) | Multiply by n | price: _.mul(1.5) |
_.push(items) | Add to array | tags: _.push(['new']) |
_.pull(item) | Remove from array | tags: _.pull('old') |
_.set(value) | Set to value | status: _.set('active') |
_.remove() | Remove field | tempField: _.remove() |
Set vs Update
`.update()` - Updates only specified fields:
// Only updates 'title', other fields remain unchanged
await db.collection('todos')
.doc('todo-123')
.update({ title: 'New Title' });`.set()` - Replaces entire document:
// Replaces entire document, removes unspecified fields
await db.collection('todos')
.doc('todo-123')
.set({ title: 'New Title', completed: false });Batch Updates
Update multiple documents efficiently:
// Update all incomplete todos assigned to a user
async function reassignTodos(oldUserId, newUserId) {
const result = await db.collection('todos')
.where({
assigneeId: oldUserId,
completed: false
})
.update({
assigneeId: newUserId,
updatedAt: new Date()
});
return result.updated;
}
const updatedCount = await reassignTodos('user-1', 'user-2');
console.log('Reassigned', updatedCount, 'todos');Delete Operations
Delete by Document ID
Delete a specific document:
// Delete by ID
const result = await db.collection('todos')
.doc('todo-id-123')
.remove();
console.log('Deleted:', result.deleted, 'document(s)');Return Value:
{
deleted: 1, // Number of documents deleted
// ... other metadata
}Delete with Conditions
Delete documents matching conditions:
// Delete all completed todos older than 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const result = await db.collection('todos')
.where({
completed: true,
completedAt: db.command.lt(thirtyDaysAgo)
})
.remove();
console.log('Deleted', result.deleted, 'old completed todos');Conditional Delete
Delete only if conditions are met:
async function deleteTodoIfOwner(todoId, userId) {
try {
const result = await db.collection('todos')
.where({
_id: todoId,
ownerId: userId // Only delete if user is owner
})
.remove();
if (result.deleted === 0) {
throw new Error('Todo not found or user is not owner');
}
return true;
} catch (error) {
console.error('Delete failed:', error);
return false;
}
}Batch Delete
Delete multiple documents:
// Delete all archived items
async function deleteArchived() {
const result = await db.collection('todos')
.where({
status: 'archived'
})
.remove();
return result.deleted;
}
const deletedCount = await deleteArchived();
console.log('Deleted', deletedCount, 'archived items');Soft Delete Pattern
Instead of permanently deleting, mark as deleted:
// Soft delete - mark as deleted instead of removing
async function softDeleteTodo(todoId) {
const result = await db.collection('todos')
.doc(todoId)
.update({
deleted: true,
deletedAt: new Date()
});
return result.updated > 0;
}
// Query only non-deleted items
async function getActiveTodos() {
const result = await db.collection('todos')
.where({
deleted: db.command.neq(true) // or: deleted: false
})
.get();
return result.data;
}Complete CRUD Examples
Todo Manager
class TodoManager {
constructor(db) {
this.db = db;
this.collection = db.collection('todos');
}
// Create
async createTodo(title, description, priority = 'medium') {
const result = await this.collection.add({
title,
description,
priority,
completed: false,
createdAt: new Date(),
updatedAt: new Date()
});
return result._id;
}
// Read (single)
async getTodo(id) {
const result = await this.collection.doc(id).get();
return result.data[0];
}
// Read (multiple)
async getTodos(filter = {}) {
const result = await this.collection
.where(filter)
.orderBy('createdAt', 'desc')
.get();
return result.data;
}
// Update
async updateTodo(id, updates) {
const result = await this.collection
.doc(id)
.update({
...updates,
updatedAt: new Date()
});
return result.updated > 0;
}
// Update status
async toggleComplete(id) {
const todo = await this.getTodo(id);
return this.updateTodo(id, {
completed: !todo.completed,
completedAt: !todo.completed ? new Date() : null
});
}
// Delete
async deleteTodo(id) {
const result = await this.collection.doc(id).remove();
return result.deleted > 0;
}
// Batch operations
async deleteCompleted() {
const result = await this.collection
.where({ completed: true })
.remove();
return result.deleted;
}
}
// Usage
const todoManager = new TodoManager(db);
// Create
const todoId = await todoManager.createTodo(
'Learn CloudBase',
'Study the database API',
'high'
);
// Read
const todo = await todoManager.getTodo(todoId);
const allTodos = await todoManager.getTodos({ completed: false });
// Update
await todoManager.updateTodo(todoId, { priority: 'urgent' });
await todoManager.toggleComplete(todoId);
// Delete
await todoManager.deleteTodo(todoId);
await todoManager.deleteCompleted();Error Handling Best Practices
async function safeCRUD() {
try {
// Create
const result = await db.collection('todos').add({
title: 'New Todo'
});
console.log('Created:', result._id);
} catch (error) {
if (error.code === 'PERMISSION_DENIED') {
console.error('No permission to create document');
} else if (error.code === 'INVALID_PARAM') {
console.error('Invalid data provided');
} else {
console.error('Unexpected error:', error);
}
throw error; // Re-throw for caller to handle
}
}Transaction Support
For operations requiring atomicity (all succeed or all fail):
// Check CloudBase documentation for transaction API
// Transactions ensure data consistency
await db.runTransaction(async transaction => {
// Read
const todo = await transaction.collection('todos').doc('id').get();
// Update based on read
await transaction.collection('todos').doc('id').update({
views: todo.data.views + 1
});
});Best Practices
1. Always handle errors: Wrap operations in try-catch 2. Validate input: Check data before database operations 3. Update timestamps: Track createdAt and updatedAt 4. Use transactions: For related operations that must succeed together 5. Batch operations: Use batch updates/deletes when possible 6. Soft deletes: Consider soft delete for important data 7. Index fields: Index frequently queried/updated fields 8. Limit updates: Only update changed fields 9. Test permissions: Ensure database security rules allow operations 10. Log operations: Track important data changes
Geolocation Queries with CloudBase
This document explains how to work with geographic data and perform location-based queries in CloudBase.
Prerequisites
⚠️ CRITICAL: Before performing any geolocation queries, you MUST create a geolocation index on the field you're querying. Queries will fail without proper indexing.
Geographic Data Types
CloudBase supports several geographic data types through db.Geo:
const db = app.database();Point (Single Location)
Represents a single geographic coordinate:
// Create a Point: longitude, latitude
const point = new db.Geo.Point(116.404, 39.915); // Tiananmen Square coordinatesNote: Coordinates are in [longitude, latitude] format (NOT latitude, longitude).
LineString (Path/Route)
Represents a path or route:
// Create a LineString (array of Points)
const line = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915), // Start
new db.Geo.Point(116.405, 39.916), // Waypoint
new db.Geo.Point(116.406, 39.917) // End
]);Polygon (Area)
Represents an enclosed area:
// Create a Polygon (array of LineStrings, first is outer boundary)
const polygon = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Must close the polygon
])
]);Note: The first and last points must be identical to close the polygon.
Storing Geographic Data
Store location data in documents:
// Add a user with location
await db.collection('users').add({
name: 'John',
location: new db.Geo.Point(116.404, 39.915),
address: 'Beijing, China'
});
// Add a delivery route
await db.collection('routes').add({
name: 'Route A',
path: new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.406, 39.917)
])
});
// Add a service area
await db.collection('serviceAreas').add({
name: 'Downtown',
area: new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.916),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915)
])
])
});Geolocation Query Operators
CloudBase provides three main geolocation query operators:
1. geoNear (Proximity Search)
Find documents near a specific location, ordered by distance:
const _ = db.command;
// Find users within 1000 meters of a location
const result = await db.collection('users').where({
location: _.geoNear({
geometry: new db.Geo.Point(116.404, 39.915), // Center point
maxDistance: 1000, // Maximum distance in meters
minDistance: 0 // Minimum distance in meters
})
}).get();
console.log('Nearby users:', result.data);Parameters:
geometry- Center point (Point object)maxDistance- Maximum distance in meters (optional)minDistance- Minimum distance in meters (optional, default: 0)
Important: Results are automatically sorted by distance (closest first).
2. geoWithin (Area Search)
Find documents within a specific geographic area:
const _ = db.command;
// Define search area
const searchArea = new db.Geo.Polygon([
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.404, 39.920),
new db.Geo.Point(116.410, 39.920),
new db.Geo.Point(116.410, 39.915),
new db.Geo.Point(116.404, 39.915)
])
]);
// Find users in the area
const result = await db.collection('users').where({
location: _.geoWithin({
geometry: searchArea
})
}).get();Use Cases:
- Find all stores in a neighborhood
- Users within a city boundary
- Deliveries in a service area
3. geoIntersects (Intersection Search)
Find documents that intersect with a specific geometry:
const _ = db.command;
// Define a path/route
const deliveryRoute = new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.410, 39.920)
]);
// Find service areas that intersect with the route
const result = await db.collection('serviceAreas').where({
area: _.geoIntersects({
geometry: deliveryRoute
})
}).get();Use Cases:
- Routes crossing service areas
- Overlapping geographic regions
- Path planning
Complete Examples
Nearby Search App
async function findNearbyPlaces(userLat, userLon, radius = 5000, category = null) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
let whereCondition = {
location: _.geoNear({
geometry: userLocation,
maxDistance: radius
})
};
// Add category filter if specified
if (category) {
whereCondition.category = category;
}
try {
const result = await db.collection('places')
.where(whereCondition)
.limit(20)
.get();
return result.data;
} catch (error) {
console.error('Nearby search failed:', error);
throw error;
}
}
// Usage
const nearbyRestaurants = await findNearbyPlaces(39.915, 116.404, 2000, 'restaurant');
console.log('Found', nearbyRestaurants.length, 'restaurants nearby');Delivery Zone Checker
async function isInDeliveryZone(userLat, userLon, storeId) {
const _ = db.command;
const userLocation = new db.Geo.Point(userLon, userLat);
try {
// Get store's delivery zone
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.deliveryZone) {
return false;
}
// Check if user location is within delivery zone
const result = await db.collection('stores')
.where({
_id: storeId,
deliveryZone: _.geoWithin({
geometry: new db.Geo.Point(userLon, userLat)
})
})
.get();
return result.data.length > 0;
} catch (error) {
console.error('Zone check failed:', error);
return false;
}
}
// Usage
const canDeliver = await isInDeliveryZone(39.915, 116.404, 'store-123');
console.log('Can deliver:', canDeliver);Distance-based Pricing
async function calculateDeliveryFee(userLat, userLon, storeId) {
const _ = db.command;
try {
// Get store location
const store = await db.collection('stores')
.doc(storeId)
.get();
if (!store.data || !store.data.location) {
throw new Error('Store location not found');
}
const userLocation = new db.Geo.Point(userLon, userLat);
// Find the store with distance
const result = await db.collection('stores')
.where({
_id: storeId,
location: _.geoNear({
geometry: userLocation,
maxDistance: 20000 // 20km max
})
})
.get();
if (result.data.length === 0) {
throw new Error('Location outside delivery range');
}
// Calculate fee based on distance
// Note: CloudBase returns distance in results
const distance = result.data[0].distance || 0;
const baseFee = 5;
const perKmFee = 2;
const deliveryFee = baseFee + (distance / 1000) * perKmFee;
return {
distance: Math.round(distance),
fee: Math.round(deliveryFee * 100) / 100
};
} catch (error) {
console.error('Fee calculation failed:', error);
throw error;
}
}
// Usage
const delivery = await calculateDeliveryFee(39.915, 116.404, 'store-123');
console.log(`Distance: ${delivery.distance}m, Fee: $${delivery.fee}`);Creating Geolocation Indexes
This is required before querying!
You need to create an index through the CloudBase console:
1. Go to your CloudBase console 2. Navigate to Database → Your Collection 3. Go to Indexes tab 4. Create a new index:
- Field:
location(or your geo field name) - Type:
geoor2dsphere
Without this index, geolocation queries will fail with an error.
Best Practices
1. Always Create Indexes: Geolocation queries require proper indexes 2. Coordinate Order: Use [longitude, latitude], not [latitude, longitude] 3. Close Polygons: First and last points in polygon must be identical 4. Distance Units: All distances are in meters 5. Limit Results: Use .limit() for large datasets 6. Error Handling: Always wrap geo queries in try-catch 7. Validate Coordinates: Ensure latitude is -90 to 90, longitude is -180 to 180 8. Combine Filters: Mix geo queries with other conditions when needed
Common Pitfalls
Wrong Coordinate Order
// ❌ WRONG - latitude first
new db.Geo.Point(39.915, 116.404)
// ✅ CORRECT - longitude first
new db.Geo.Point(116.404, 39.915)Unclosed Polygon
// ❌ WRONG - not closed
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915)
])
// ✅ CORRECT - first equals last
new db.Geo.LineString([
new db.Geo.Point(116.404, 39.915),
new db.Geo.Point(116.405, 39.916),
new db.Geo.Point(116.405, 39.915),
new db.Geo.Point(116.404, 39.915) // Closes polygon
])Missing Index
// ❌ Will fail without geo index
await db.collection('users').where({
location: _.geoNear({ geometry: point })
}).get()
// ✅ Create index first in console, then queryPerformance Considerations
1. Index Size: Geolocation indexes can be large; monitor storage 2. Query Radius: Smaller radius queries are faster 3. Result Limits: Always use .limit() to prevent large result sets 4. Combine Conditions: Filter by category/type first, then location 5. Cache Results: Cache frequently accessed location data
React Example Component
import { useState, useEffect } from 'react';
function NearbyPlaces({ userLat, userLon }) {
const [places, setPlaces] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadNearbyPlaces();
}, [userLat, userLon]);
async function loadNearbyPlaces() {
setLoading(true);
try {
const _ = db.command;
const result = await db.collection('places')
.where({
location: _.geoNear({
geometry: new db.Geo.Point(userLon, userLat),
maxDistance: 5000
})
})
.limit(10)
.get();
setPlaces(result.data);
} catch (error) {
console.error('Failed to load places:', error);
} finally {
setLoading(false);
}
}
if (loading) return <div>Loading nearby places...</div>;
return (
<div>
<h2>Nearby Places</h2>
<ul>
{places.map(place => (
<li key={place._id}>
{place.name} - {Math.round(place.distance)}m away
</li>
))}
</ul>
</div>
);
}Pagination with CloudBase
This document explains how to implement pagination for large datasets in CloudBase document database.
Basic Pagination Concepts
Pagination allows you to retrieve large datasets in smaller, manageable chunks (pages).
Key Parameters:
pageSize- Number of records per pagepageNum- Current page number (1-based)skip()- Number of records to skiplimit()- Maximum records to return
Simple Pagination Implementation
Basic Page-based Query
const pageSize = 10; // Records per page
const pageNum = 1; // Current page (1-based)
const result = await db.collection('todos')
.orderBy('createdAt', 'desc')
.skip((pageNum - 1) * pageSize)
.limit(pageSize)
.get();
console.log('Page', pageNum, 'data:', result.data);Calculation Formula
// For page N:
const skip = (pageNum - 1) * pageSize;
const limit = pageSize;Complete Pagination Function
Here's a reusable pagination function:
/**
* Paginate through a collection
* @param {string} collectionName - Name of the collection
* @param {number} page - Page number (1-based)
* @param {number} pageSize - Records per page
* @param {object} whereConditions - Query conditions (optional)
* @param {string} sortField - Field to sort by (optional)
* @param {string} sortDirection - 'asc' or 'desc' (optional)
*/
async function paginateCollection(
collectionName,
page = 1,
pageSize = 10,
whereConditions = {},
sortField = 'createdAt',
sortDirection = 'desc'
) {
const skip = (page - 1) * pageSize;
let query = db.collection(collectionName);
// Apply conditions if provided
if (Object.keys(whereConditions).length > 0) {
query = query.where(whereConditions);
}
// Apply sorting
if (sortField) {
query = query.orderBy(sortField, sortDirection);
}
// Apply pagination
const result = await query
.skip(skip)
.limit(pageSize)
.get();
return {
data: result.data,
page: page,
pageSize: pageSize,
hasMore: result.data.length === pageSize
};
}
// Usage
const pageData = await paginateCollection('todos', 2, 20, { status: 'active' });
console.log('Page 2 data:', pageData);Getting Total Count
To show "Page X of Y", you need the total count:
async function paginateWithCount(collectionName, page, pageSize, whereConditions = {}) {
const skip = (page - 1) * pageSize;
// Get paginated data
const dataQuery = db.collection(collectionName);
const countQuery = db.collection(collectionName);
if (Object.keys(whereConditions).length > 0) {
dataQuery.where(whereConditions);
countQuery.where(whereConditions);
}
// Execute both queries
const [dataResult, countResult] = await Promise.all([
dataQuery
.orderBy('createdAt', 'desc')
.skip(skip)
.limit(pageSize)
.get(),
countQuery.count()
]);
const totalCount = countResult.total;
const totalPages = Math.ceil(totalCount / pageSize);
return {
data: dataResult.data,
pagination: {
currentPage: page,
pageSize: pageSize,
totalCount: totalCount,
totalPages: totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
}
// Usage
const result = await paginateWithCount('todos', 1, 10, { status: 'active' });
console.log(`Page ${result.pagination.currentPage} of ${result.pagination.totalPages}`);
console.log(`Total items: ${result.pagination.totalCount}`);Cursor-based Pagination
For real-time data or better performance, use cursor-based pagination:
/**
* Cursor-based pagination using a field value as cursor
*/
async function paginateWithCursor(collectionName, cursor = null, pageSize = 10) {
const _ = db.command;
let query = db.collection(collectionName);
// If cursor exists, query records after cursor
if (cursor) {
query = query.where({
createdAt: _.lt(cursor) // Assuming descending order
});
}
const result = await query
.orderBy('createdAt', 'desc')
.limit(pageSize + 1) // Fetch one extra to check if more exists
.get();
const hasMore = result.data.length > pageSize;
const data = hasMore ? result.data.slice(0, pageSize) : result.data;
const nextCursor = hasMore ? data[data.length - 1].createdAt : null;
return {
data: data,
nextCursor: nextCursor,
hasMore: hasMore
};
}
// Usage - First page
const firstPage = await paginateWithCursor('todos', null, 10);
console.log('First page:', firstPage.data);
// Next page using cursor
const secondPage = await paginateWithCursor('todos', firstPage.nextCursor, 10);
console.log('Second page:', secondPage.data);React Component Example
Here's how to implement pagination in a React component:
import { useState, useEffect } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const pageSize = 10;
useEffect(() => {
loadPage(currentPage);
}, [currentPage]);
async function loadPage(page) {
setLoading(true);
try {
const result = await paginateWithCount('todos', page, pageSize);
setTodos(result.data);
setTotalPages(result.pagination.totalPages);
} catch (error) {
console.error('Failed to load todos:', error);
} finally {
setLoading(false);
}
}
function goToNextPage() {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
}
function goToPrevPage() {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
}
return (
<div>
<h2>Todos</h2>
{loading ? (
<p>Loading...</p>
) : (
<>
<ul>
{todos.map(todo => (
<li key={todo._id}>{todo.title}</li>
))}
</ul>
<div className="pagination">
<button
onClick={goToPrevPage}
disabled={currentPage === 1}
>
Previous
</button>
<span>Page {currentPage} of {totalPages}</span>
<button
onClick={goToNextPage}
disabled={currentPage === totalPages}
>
Next
</button>
</div>
</>
)}
</div>
);
}Infinite Scroll Pattern
For infinite scroll UI:
function useInfiniteScroll(collectionName, pageSize = 20) {
const [items, setItems] = useState([]);
const [cursor, setCursor] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
async function loadMore() {
if (loading || !hasMore) return;
setLoading(true);
try {
const result = await paginateWithCursor(collectionName, cursor, pageSize);
setItems(prev => [...prev, ...result.data]);
setCursor(result.nextCursor);
setHasMore(result.hasMore);
} catch (error) {
console.error('Failed to load more:', error);
} finally {
setLoading(false);
}
}
return { items, loadMore, hasMore, loading };
}Performance Considerations
1. Index Sort Fields: Ensure fields used in orderBy() are indexed 2. Reasonable Page Size: 10-50 items per page is typical 3. Count Caching: Cache total count if it doesn't change often 4. Skip Limits: Very large skip() values can be slow; consider cursor-based pagination 5. Parallel Queries: Use Promise.all() for count and data queries
Best Practices
1. Always specify an orderBy() for consistent pagination 2. Use cursor-based pagination for real-time feeds 3. Cache page results when appropriate 4. Show loading states during page transitions 5. Handle empty results gracefully 6. Validate page numbers (must be >= 1) 7. Consider using URL query parameters for page state 8. Implement error handling and retry logic
CloudBase NoSQL Security Rules for Mini Programs
Use this reference when a Mini Program collection needs permission design or when client-side queries are being rejected by collection rules.
Core ideas
- Mini Program users usually appear as
auth.openidin security rules. - Document ownership is typically checked through
doc._openid. - Security rules validate whether the request shape is allowed; they do not filter the result set after the query runs.
- If a query is rejected, inspect the rule/query relationship before rewriting the whole feature.
Ownership pattern
A common rule for Mini Program user-owned data is:
{
"read": "doc._openid == auth.openid",
"write": "doc._openid == auth.openid"
}This means:
- users can read their own documents
- users can write their own documents
- queries usually need to carry the ownership condition explicitly when the rule model requires it
_openid handling
_openid is managed by the CloudBase SDK.
Correct:
await db.collection("todos").add({
title: "Buy milk",
completed: false
});Wrong:
await db.collection("todos").add({
title: "Buy milk",
_openid: "manual-value"
});Do not set _openid manually in write payloads.
Recommended workflow
1. Decide whether simple permissions are enough. 2. If custom logic is required, read or write the collection rule explicitly. 3. Test queries with the same ownership constraints the rule expects. 4. If the product needs privileged global access, move that path to backend code instead of widening Mini Program direct-access rules.
Related references
- For the full rule system and examples, also read
../no-sql-web-sdk/security-rules.md. - For Mini Program identity flow, read
../auth-wechat/SKILL.md.