Read and Write CloudBase Database in WeChat Mini Program
In one sentence: In a WeChat Mini Program already integrated with CloudBase Custom Login, use
app.database()for collection CRUD, conditional queries, and real-timewatchsubscriptions — all reads and writes are constrained by the collection's Permission Mode.Estimated time: 30 minutes | Difficulty: Advanced
Applicable Scenarios
This is the next step after add-auth-wechat-miniprogram. The prerequisite has already installed @cloudbase/js-sdk and obtained a login state; this article covers how to read and write data.
- Applicable: Standalone CloudBase environment + WeChat Mini Program frontend, login state already established
- Not applicable: Using the
wx.cloudWeChat Cloud Development system (that system has its ownwx.cloud.database(), with a similar but incompatible API) - Not applicable: Financial/inventory scenarios requiring extremely high consistency. Frontend direct database access uses "permission-constrained SDK calls"; under extreme concurrency, it is recommended to route writes through Cloud Functions
Prerequisites
| Dependency | Version |
|---|---|
@cloudbase/js-sdk | 2.27.3 (already installed in the prerequisite recipe) |
@cloudbase/adapter-wx_mp | 1.3.1 |
| WeChat DevTools | ≥ 1.06.x |
Also required:
- Already completed add-auth-wechat-miniprogram in the Mini Program, with
auth.hasLoginState()returning true - CloudBase Console access to "Database → Collection Management" for the target collection
Step 1: Create a Collection and Select a Permission Mode
Go to CloudBase Console → Database → Collection Management → Create Collection. For example, create a collection named todos.
After creation, click into it; the "Data Permission" button appears in the upper left — this is the key part of this step. CloudBase provides four built-in Permission Modes:
| Permission Mode | Who Can Read | Who Can Write |
|---|---|---|
| All users can read, only creator can write | All logged-in users | Only records where _openid equals the current user's openid |
| Only creator can read and write | Only records where _openid equals the current user's openid | Same as left |
| Only admin can read and write | Cloud Functions and Console | Same as left |
| Custom Security Rules | See secure-database-multi-tenant-rules | Same as left |
"Only creator can read and write" is the default choice for todo / personal notes scenarios. CloudBase automatically adds an _openid field to documents on write; subsequent reads and writes are filtered by this field, so no need to write where({ _openid: ... }) in business code.
If "Only admin can read and write" is selected, all reads and writes from the Mini Program frontend will return UNAUTHORIZED; those must be routed through Cloud Functions, which is not covered here.
Step 2: Get a db Instance in the Mini Program
Continuing from the miniprogram/libs/cloudbase.js code in add-auth-wechat-miniprogram, add one line:
import cloudbase from '@cloudbase/js-sdk';
import adapter from '@cloudbase/adapter-wx_mp';
cloudbase.useAdapters(adapter);
const app = cloudbase.init({
env: 'your-env-id',
});
export const auth = app.auth();
export const db = app.database();
export default app;
The db returned by app.database() is a lightweight object that can be imported repeatedly without concern about creating multiple instances.
To prevent a page from issuing queries before the login state is ready, it is recommended to await ensureLogin() before using db:
import { ensureLogin } from './login';
import { db } from './cloudbase';
export async function getTodos() {
await ensureLogin();
return db.collection('todos').where({}).orderBy('createdAt', 'desc').get();
}
Step 3: CRUD
Create
const res = await db.collection('todos').add({
title: 'Write recipe',
done: false,
createdAt: db.serverDate(),
});
console.log('inserted id:', res.id);
db.serverDate() is a server-side timestamp placeholder replaced with the current database time on write, preventing client clock drift from causing sort issues.
Read
// Get all
const all = await db.collection('todos').get();
// Conditional + sort + pagination
const page = await db
.collection('todos')
.where({ done: false })
.orderBy('createdAt', 'desc')
.skip(0)
.limit(20)
.get();
console.log('docs:', page.data);
The return structure is { data: [...], requestId: '...' }. The document list is in data. A common mistake is writing page.docs (that is the field in watch callbacks, see Step 4).
For complex conditions, use db.command:
const _ = db.command;
const recent = await db
.collection('todos')
.where({
createdAt: _.gte(new Date(Date.now() - 7 * 24 * 3600 * 1000)),
done: _.eq(false),
})
.get();
Update
Update a single document by _id:
await db.collection('todos').doc('todo-id').update({
done: true,
updatedAt: db.serverDate(),
});
update performs a partial update, overwriting only the provided fields. To replace the entire document (including deleting fields that are not passed), use set:
await db.collection('todos').doc('todo-id').set({
title: 'Full replacement',
done: true,
// Fields not passed (e.g., the original createdAt) will be removed
});
Bulk update by condition:
await db.collection('todos').where({ done: true }).update({
archived: true,
});
Delete
// By ID
await db.collection('todos').doc('todo-id').remove();
// By condition
await db.collection('todos').where({ archived: true }).remove();
Step 4: Real-time watch Subscription
watch lets the frontend subscribe to the result set of a query; whenever records change, updates are pushed via onChange.
// pages/todos/todos.js
import { db } from '../../libs/cloudbase';
Page({
data: { todos: [] },
onLoad() {
this.watcher = db
.collection('todos')
.where({ done: false })
.watch({
onChange: (snapshot) => {
// snapshot.docs is the full current result set for this query
this.setData({ todos: snapshot.docs });
},
onError: (err) => {
console.error('[watch] error', err);
},
});
},
onUnload() {
// Must close when the page is destroyed, otherwise memory leaks and duplicate pushes occur
if (this.watcher) {
this.watcher.close();
this.watcher = null;
}
},
});
Four key points:
watch()returns acloser; subscription can only be cancelled viacloser.close(), notremoveListener- The first callback delivers an "initial snapshot" containing all documents matching the query, similar to a
get() - On every subsequent add / update / delete of a matching document,
onChangefires;snapshot.docsis the complete result set after the change, not a diff onUnloadon pages /detachedon components must callclose(); leaving it open too long triggers the server-side connection limit
To get the diff (only the changed documents), use snapshot.docChanges:
onChange: (snapshot) => {
for (const change of snapshot.docChanges) {
// change.dataType: 'init' | 'update' | 'add' | 'remove'
console.log(change.dataType, change.doc);
}
};
Verification
- Compile and run in WeChat DevTools
- On the todos page, call
addto insert a record; the Console should print the inserted id - Console → Database → todos collection: confirm the data has an
_openidfield equal to the current user's openid - Directly edit a record's
titlein the Console; the Mini Program page should immediately reflect the change (watch is working) - Exit the page; the Console should no longer log watch push messages
Common Errors
| Error code / message | Cause | Fix |
|---|---|---|
UNAUTHORIZED / permission denied | Collection Permission Mode is "Only admin can read and write", or the document's _openid does not match the current user | Check the collection Permission Mode, or confirm the login state is valid at write time |
collection not exist | The collection has not been created in the Console | Create it manually in the Console; the SDK does not auto-create collections |
INVALID_ARGUMENT with field type info | The same field has inconsistent types across documents (e.g., some string, some number) | CloudBase does not enforce schema, but mixed types cause errors during comparison/sorting. Unify types, or call Number(x) before writing |
watch onChange not firing | The page closer was likely overwritten multiple times in onLoad without closing the old one | Guard with if (this.watcher) this.watcher.close() to keep only one active subscription |
WX_ERR_NETWORK | The Mini Program's allowed domains are not configured | WeChat Official Account Platform → Development Management → Server Domain → add *.tcloudbase.com to request allowed domains |
For error code definitions, see error-code.
Related Documentation
- Web SDK Database API — complete reference for
collection / doc / where / get - Database Permission Management — description of the four built-in modes
- Realtime Data Push — protocol-level details of
watch - add-auth-wechat-miniprogram — prerequisite: login integration
Next Steps
- Multi-tenant isolation: secure-database-multi-tenant-rules
- Trigger Subscribe Messages on writes: add-subscribe-message-cloud-function
- Run daily reports after data updates: schedule-cloud-function-cron-job