Data Storage
This guide covers data storage solutions in mini-program development, including local storage, cloud storage, database integration, and data synchronization strategies.
📋 Table of Contents
- Local Storage
- Cloud Storage
- Database Integration
- Data Synchronization
- Caching Strategies
- Security Considerations
- Performance Optimization
- Best Practices
💾 Local Storage
Storage APIs
javascript
// Synchronous storage
wx.setStorageSync('key', 'value')
const value = wx.getStorageSync('key')
wx.removeStorageSync('key')
wx.clearStorageSync()
// Asynchronous storage
wx.setStorage({
key: 'userInfo',
data: {
name: 'John Doe',
age: 30
},
success: () => console.log('Data saved'),
fail: (error) => console.error('Save failed:', error)
})
wx.getStorage({
key: 'userInfo',
success: (res) => console.log('Data:', res.data),
fail: (error) => console.error('Get failed:', error)
})
Storage Wrapper Class
javascript
class StorageManager {
static set(key, value, encrypt = false) {
try {
const data = encrypt ? this.encrypt(value) : value
wx.setStorageSync(key, data)
return true
} catch (error) {
console.error('Storage set error:', error)
return false
}
}
static get(key, defaultValue = null, decrypt = false) {
try {
const data = wx.getStorageSync(key)
if (data === '') return defaultValue
return decrypt ? this.decrypt(data) : data
} catch (error) {
console.error('Storage get error:', error)
return defaultValue
}
}
static remove(key) {
try {
wx.removeStorageSync(key)
return true
} catch (error) {
console.error('Storage remove error:', error)
return false
}
}
static clear() {
try {
wx.clearStorageSync()
return true
} catch (error) {
console.error('Storage clear error:', error)
return false
}
}
static getInfo() {
try {
return wx.getStorageInfoSync()
} catch (error) {
console.error('Storage info error:', error)
return null
}
}
static encrypt(data) {
// Simple encryption (use proper encryption in production)
return btoa(JSON.stringify(data))
}
static decrypt(data) {
try {
return JSON.parse(atob(data))
} catch (error) {
return data
}
}
}
// Usage
StorageManager.set('user', { name: 'John', age: 30 })
const user = StorageManager.get('user', {})
Data Models
javascript
class UserModel {
constructor(data = {}) {
this.id = data.id || null
this.name = data.name || ''
this.email = data.email || ''
this.avatar = data.avatar || ''
this.preferences = data.preferences || {}
this.createdAt = data.createdAt || new Date().toISOString()
this.updatedAt = data.updatedAt || new Date().toISOString()
}
save() {
this.updatedAt = new Date().toISOString()
StorageManager.set(`user_${this.id}`, this.toJSON())
return this
}
static find(id) {
const data = StorageManager.get(`user_${id}`)
return data ? new UserModel(data) : null
}
static findAll() {
const info = StorageManager.getInfo()
const users = []
if (info && info.keys) {
info.keys.forEach(key => {
if (key.startsWith('user_')) {
const data = StorageManager.get(key)
if (data) users.push(new UserModel(data))
}
})
}
return users
}
delete() {
return StorageManager.remove(`user_${this.id}`)
}
toJSON() {
return {
id: this.id,
name: this.name,
email: this.email,
avatar: this.avatar,
preferences: this.preferences,
createdAt: this.createdAt,
updatedAt: this.updatedAt
}
}
}
☁️ Cloud Storage
WeChat Cloud Storage
javascript
// Initialize cloud
wx.cloud.init({
env: 'your-cloud-env-id'
})
// Upload file
function uploadFile(filePath) {
return wx.cloud.uploadFile({
cloudPath: `images/${Date.now()}.jpg`,
filePath: filePath
})
}
// Download file
function downloadFile(fileID) {
return wx.cloud.downloadFile({
fileID: fileID
})
}
// Delete file
function deleteFile(fileIDList) {
return wx.cloud.deleteFile({
fileList: fileIDList
})
}
// Get temporary URL
function getTempFileURL(fileIDList) {
return wx.cloud.getTempFileURL({
fileList: fileIDList
})
}
File Upload Manager
javascript
class FileUploadManager {
constructor() {
this.uploadQueue = []
this.maxConcurrent = 3
this.currentUploads = 0
}
async uploadFile(filePath, cloudPath, onProgress) {
return new Promise((resolve, reject) => {
const uploadTask = {
filePath,
cloudPath,
onProgress,
resolve,
reject
}
this.uploadQueue.push(uploadTask)
this.processQueue()
})
}
async processQueue() {
if (this.currentUploads >= this.maxConcurrent || this.uploadQueue.length === 0) {
return
}
const task = this.uploadQueue.shift()
this.currentUploads++
try {
const result = await wx.cloud.uploadFile({
cloudPath: task.cloudPath,
filePath: task.filePath,
success: (res) => {
if (task.onProgress) {
task.onProgress(100)
}
task.resolve(res)
},
fail: (error) => {
task.reject(error)
}
})
} catch (error) {
task.reject(error)
} finally {
this.currentUploads--
this.processQueue() // Process next in queue
}
}
async uploadMultiple(files) {
const promises = files.map(file =>
this.uploadFile(file.path, file.cloudPath, file.onProgress)
)
return Promise.allSettled(promises)
}
}
// Usage
const uploadManager = new FileUploadManager()
uploadManager.uploadFile(
'/temp/image.jpg',
'user-uploads/image.jpg',
(progress) => console.log(`Upload progress: ${progress}%`)
).then(result => {
console.log('Upload successful:', result.fileID)
}).catch(error => {
console.error('Upload failed:', error)
})
🗄️ Database Integration
Cloud Database Operations
javascript
// Get database reference
const db = wx.cloud.database()
const users = db.collection('users')
// Add document
async function addUser(userData) {
try {
const result = await users.add({
data: {
...userData,
createTime: db.serverDate()
}
})
console.log('User added:', result._id)
return result
} catch (error) {
console.error('Add user failed:', error)
throw error
}
}
// Query documents
async function getUsers(limit = 20) {
try {
const result = await users
.orderBy('createTime', 'desc')
.limit(limit)
.get()
return result.data
} catch (error) {
console.error('Get users failed:', error)
return []
}
}
// Update document
async function updateUser(userId, updates) {
try {
const result = await users.doc(userId).update({
data: {
...updates,
updateTime: db.serverDate()
}
})
return result
} catch (error) {
console.error('Update user failed:', error)
throw error
}
}
// Delete document
async function deleteUser(userId) {
try {
const result = await users.doc(userId).remove()
return result
} catch (error) {
console.error('Delete user failed:', error)
throw error
}
}
Database Query Builder
javascript
class QueryBuilder {
constructor(collection) {
this.collection = collection
this.query = {}
this.sortOptions = []
this.limitCount = null
this.skipCount = null
}
where(field, operator, value) {
if (!this.query[field]) {
this.query[field] = {}
}
switch (operator) {
case '==':
this.query[field] = value
break
case '!=':
this.query[field] = db.command.neq(value)
break
case '>':
this.query[field] = db.command.gt(value)
break
case '>=':
this.query[field] = db.command.gte(value)
break
case '<':
this.query[field] = db.command.lt(value)
break
case '<=':
this.query[field] = db.command.lte(value)
break
case 'in':
this.query[field] = db.command.in(value)
break
case 'contains':
this.query[field] = db.RegExp({
regexp: value,
options: 'i'
})
break
}
return this
}
orderBy(field, direction = 'asc') {
this.sortOptions.push({ field, direction })
return this
}
limit(count) {
this.limitCount = count
return this
}
skip(count) {
this.skipCount = count
return this
}
async get() {
let query = this.collection.where(this.query)
// Apply sorting
this.sortOptions.forEach(sort => {
query = query.orderBy(sort.field, sort.direction)
})
// Apply pagination
if (this.skipCount) {
query = query.skip(this.skipCount)
}
if (this.limitCount) {
query = query.limit(this.limitCount)
}
try {
const result = await query.get()
return result.data
} catch (error) {
console.error('Query failed:', error)
return []
}
}
}
// Usage
const userQuery = new QueryBuilder(db.collection('users'))
const activeUsers = await userQuery
.where('status', '==', 'active')
.where('age', '>=', 18)
.orderBy('createTime', 'desc')
.limit(10)
.get()
🔄 Data Synchronization
Sync Manager
javascript
class SyncManager {
constructor() {
this.syncQueue = []
this.isSyncing = false
this.lastSyncTime = StorageManager.get('lastSyncTime', 0)
}
async syncData() {
if (this.isSyncing) return
this.isSyncing = true
try {
// Sync local changes to server
await this.syncLocalChanges()
// Sync server changes to local
await this.syncServerChanges()
this.lastSyncTime = Date.now()
StorageManager.set('lastSyncTime', this.lastSyncTime)
console.log('Sync completed successfully')
} catch (error) {
console.error('Sync failed:', error)
} finally {
this.isSyncing = false
}
}
async syncLocalChanges() {
const pendingChanges = StorageManager.get('pendingChanges', [])
for (const change of pendingChanges) {
try {
await this.applyChangeToServer(change)
// Remove from pending changes
const index = pendingChanges.indexOf(change)
if (index > -1) {
pendingChanges.splice(index, 1)
}
} catch (error) {
console.error('Failed to sync change:', change, error)
}
}
StorageManager.set('pendingChanges', pendingChanges)
}
async syncServerChanges() {
try {
const serverChanges = await this.getServerChanges(this.lastSyncTime)
for (const change of serverChanges) {
await this.applyChangeLocally(change)
}
} catch (error) {
console.error('Failed to sync server changes:', error)
}
}
addPendingChange(change) {
const pendingChanges = StorageManager.get('pendingChanges', [])
pendingChanges.push({
...change,
timestamp: Date.now(),
id: this.generateId()
})
StorageManager.set('pendingChanges', pendingChanges)
}
async applyChangeToServer(change) {
// Implementation depends on your server API
const response = await wx.request({
url: 'https://api.example.com/sync',
method: 'POST',
data: change
})
if (response.statusCode !== 200) {
throw new Error(`Server error: ${response.statusCode}`)
}
return response.data
}
async getServerChanges(since) {
const response = await wx.request({
url: `https://api.example.com/changes?since=${since}`,
method: 'GET'
})
if (response.statusCode === 200) {
return response.data.changes || []
}
return []
}
async applyChangeLocally(change) {
switch (change.type) {
case 'create':
StorageManager.set(change.key, change.data)
break
case 'update':
const existing = StorageManager.get(change.key, {})
StorageManager.set(change.key, { ...existing, ...change.data })
break
case 'delete':
StorageManager.remove(change.key)
break
}
}
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
}
// Usage
const syncManager = new SyncManager()
// Add change to sync queue
syncManager.addPendingChange({
type: 'update',
key: 'user_123',
data: { name: 'Updated Name' }
})
// Sync data
syncManager.syncData()
🚀 Caching Strategies
Cache Manager
javascript
class CacheManager {
constructor() {
this.cache = new Map()
this.maxSize = 100
this.ttl = 5 * 60 * 1000 // 5 minutes
}
set(key, value, ttl = this.ttl) {
// Remove oldest entries if cache is full
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, {
value,
timestamp: Date.now(),
ttl
})
}
get(key) {
const item = this.cache.get(key)
if (!item) return null
// Check if expired
if (Date.now() - item.timestamp > item.ttl) {
this.cache.delete(key)
return null
}
return item.value
}
has(key) {
return this.get(key) !== null
}
delete(key) {
return this.cache.delete(key)
}
clear() {
this.cache.clear()
}
cleanup() {
const now = Date.now()
for (const [key, item] of this.cache.entries()) {
if (now - item.timestamp > item.ttl) {
this.cache.delete(key)
}
}
}
}
// Global cache instance
const cache = new CacheManager()
// Cleanup expired entries every minute
setInterval(() => {
cache.cleanup()
}, 60 * 1000)
HTTP Cache Wrapper
javascript
class HTTPCache {
constructor(cacheManager) {
this.cache = cacheManager
}
async request(options) {
const cacheKey = this.getCacheKey(options)
// Try to get from cache first
const cached = this.cache.get(cacheKey)
if (cached) {
console.log('Cache hit:', cacheKey)
return cached
}
try {
// Make actual request
const response = await wx.request(options)
// Cache successful responses
if (response.statusCode === 200) {
this.cache.set(cacheKey, response, this.getTTL(options))
}
return response
} catch (error) {
console.error('Request failed:', error)
throw error
}
}
getCacheKey(options) {
return `${options.method || 'GET'}_${options.url}_${JSON.stringify(options.data || {})}`
}
getTTL(options) {
// Different TTL for different endpoints
if (options.url.includes('/user/profile')) {
return 10 * 60 * 1000 // 10 minutes
}
if (options.url.includes('/config')) {
return 60 * 60 * 1000 // 1 hour
}
return 5 * 60 * 1000 // 5 minutes default
}
}
// Usage
const httpCache = new HTTPCache(cache)
const userData = await httpCache.request({
url: 'https://api.example.com/user/profile',
method: 'GET'
})
🔒 Security Considerations
Data Encryption
javascript
class SecureStorage {
constructor(secretKey) {
this.secretKey = secretKey
}
encrypt(data) {
// Simple XOR encryption (use proper encryption in production)
const jsonString = JSON.stringify(data)
let encrypted = ''
for (let i = 0; i < jsonString.length; i++) {
const charCode = jsonString.charCodeAt(i)
const keyChar = this.secretKey.charCodeAt(i % this.secretKey.length)
encrypted += String.fromCharCode(charCode ^ keyChar)
}
return btoa(encrypted)
}
decrypt(encryptedData) {
try {
const encrypted = atob(encryptedData)
let decrypted = ''
for (let i = 0; i < encrypted.length; i++) {
const charCode = encrypted.charCodeAt(i)
const keyChar = this.secretKey.charCodeAt(i % this.secretKey.length)
decrypted += String.fromCharCode(charCode ^ keyChar)
}
return JSON.parse(decrypted)
} catch (error) {
console.error('Decryption failed:', error)
return null
}
}
setSecure(key, value) {
const encrypted = this.encrypt(value)
StorageManager.set(key, encrypted)
}
getSecure(key, defaultValue = null) {
const encrypted = StorageManager.get(key)
if (!encrypted) return defaultValue
const decrypted = this.decrypt(encrypted)
return decrypted !== null ? decrypted : defaultValue
}
}
// Usage
const secureStorage = new SecureStorage('your-secret-key')
secureStorage.setSecure('sensitive_data', { token: 'abc123' })
const data = secureStorage.getSecure('sensitive_data')
🎯 Best Practices
Data Validation
javascript
class DataValidator {
static validateUser(userData) {
const errors = []
if (!userData.name || userData.name.trim().length < 2) {
errors.push('Name must be at least 2 characters')
}
if (!userData.email || !this.isValidEmail(userData.email)) {
errors.push('Valid email is required')
}
if (userData.age && (userData.age < 0 || userData.age > 150)) {
errors.push('Age must be between 0 and 150')
}
return {
isValid: errors.length === 0,
errors
}
}
static isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return emailRegex.test(email)
}
static sanitizeInput(input) {
if (typeof input !== 'string') return input
return input
.trim()
.replace(/[<>]/g, '') // Remove potential HTML tags
.substring(0, 1000) // Limit length
}
}
Error Handling
javascript
class StorageError extends Error {
constructor(message, code, originalError) {
super(message)
this.name = 'StorageError'
this.code = code
this.originalError = originalError
}
}
class RobustStorage {
static async safeOperation(operation, fallback = null) {
try {
return await operation()
} catch (error) {
console.error('Storage operation failed:', error)
// Report error to monitoring service
this.reportError(error)
return fallback
}
}
static async set(key, value) {
return this.safeOperation(async () => {
// Validate input
if (!key || typeof key !== 'string') {
throw new StorageError('Invalid key', 'INVALID_KEY')
}
// Check storage quota
const info = wx.getStorageInfoSync()
if (info.currentSize > 8 * 1024) { // 8MB limit
throw new StorageError('Storage quota exceeded', 'QUOTA_EXCEEDED')
}
wx.setStorageSync(key, value)
return true
}, false)
}
static async get(key, defaultValue = null) {
return this.safeOperation(async () => {
const value = wx.getStorageSync(key)
return value !== '' ? value : defaultValue
}, defaultValue)
}
static reportError(error) {
// Send error to monitoring service
console.error('Storage error reported:', error)
}
}
Effective data storage is crucial for mini-program performance and user experience. Choose the right storage solution based on your data requirements, implement proper caching strategies, and always consider security and error handling in your storage implementation.