Caching¶
WebORM includes a built-in intelligent caching system that automatically optimizes your application's performance by reducing database calls.
How It Works¶
The caching system operates transparently:
- First Query: Hits the database and caches the result
- Subsequent Queries: Returns cached data if still valid
- Automatic Invalidation: Cache is cleared when data changes
- TTL Expiration: Cache expires after 5 minutes
Automatic Caching¶
All query operations are automatically cached:
const db = await orm.init([{
name: 'posts',
schema: {
title: { type: 'string', required: true },
content: { type: 'string', required: true }
}
}]);
const postsTable = db.table('posts');
// First call - hits database
const posts1 = await postsTable.all();
// Second call - uses cache (instant)
const posts2 = await postsTable.all();
// Third call - still uses cache
const posts3 = await postsTable.find(['equal("published", true)']);
Cache Invalidation¶
Cache is automatically invalidated when data changes:
// These calls use cache
const posts1 = await postsTable.all();
const posts2 = await postsTable.all();
// This mutation invalidates the cache
await postsTable.create({
title: 'New Post',
content: 'Fresh content'
});
// This call hits the database again
const posts3 = await postsTable.all();
Mutation Operations That Invalidate Cache¶
create()- Creating new documentsupdate()- Updating existing documentsdelete()- Deleting documentsbulkCreate()- Bulk operationsbulkUpdate()- Bulk operationsbulkDelete()- Bulk operations
Manual Cache Control¶
Check Cache Status¶
const postsTable = db.table('posts');
// Check if cache is valid/fresh
if (postsTable.isUpdated()) {
console.log('Cache is fresh - next query will use cache');
} else {
console.log('Cache is stale - next query will hit database');
}
Manual Cache Invalidation¶
const postsTable = db.table('posts');
// Force cache invalidation
postsTable.setUpdated(false);
// Next query will hit the database
const freshPosts = await postsTable.all();
Manual Cache Refresh¶
const postsTable = db.table('posts');
// Invalidate and immediately refresh
postsTable.setUpdated(false);
const freshPosts = await postsTable.all();
// Cache is now fresh again
console.log(postsTable.isUpdated()); // true
Cache TTL (Time To Live)¶
Cache entries automatically expire after 5 minutes:
// First call at 10:00 AM
const posts1 = await postsTable.all(); // Hits database
// Call at 10:02 AM
const posts2 = await postsTable.all(); // Uses cache
// Call at 10:06 AM (6 minutes later)
const posts3 = await postsTable.all(); // Hits database (TTL expired)
Realtime Integration¶
When using realtime listeners, cache is automatically invalidated when events are received:
const postsTable = db.table('posts');
// Set up realtime listener
const unsubscribe = postsTable.listenToDocuments((event) => {
console.log('Data changed, cache invalidated automatically');
});
// These calls use cache
const posts1 = await postsTable.all();
const posts2 = await postsTable.all();
// When another user creates a post, the realtime event
// automatically invalidates our cache
// Next call will get fresh data
const posts3 = await postsTable.all();
React Integration¶
Basic Caching with React¶
import { useState, useEffect } from 'react';
function PostsList({ db }) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadPosts = async () => {
setLoading(true);
// This will use cache if available
const allPosts = await db.table('posts').all();
setPosts(allPosts);
setLoading(false);
};
loadPosts();
}, [db]);
const createPost = async (postData) => {
// This invalidates cache automatically
const newPost = await db.table('posts').create(postData);
// Reload to get fresh data (will hit database)
const updatedPosts = await db.table('posts').all();
setPosts(updatedPosts);
};
return (
<div>
{loading ? 'Loading...' : posts.map(post => (
<div key={post.$id}>{post.title}</div>
))}
</div>
);
}
Advanced React Hook¶
import { useState, useEffect, useCallback } from 'react';
function useCachedData(table, dependencies = []) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [cacheStatus, setCacheStatus] = useState(true);
const refresh = useCallback(async (force = false) => {
if (force) {
table.setUpdated(false);
}
setCacheStatus(table.isUpdated());
setLoading(true);
const result = await table.all();
setData(result);
setLoading(false);
setCacheStatus(table.isUpdated());
}, [table, ...dependencies]);
useEffect(() => {
refresh();
}, [refresh]);
return {
data,
loading,
refresh,
isCached: cacheStatus,
invalidateCache: () => table.setUpdated(false)
};
}
// Usage
function PostsList({ db }) {
const {
data: posts,
loading,
refresh,
isCached,
invalidateCache
} = useCachedData(db.table('posts'));
return (
<div>
<div>
Cache Status: {isCached ? 'Fresh' : 'Stale'}
<button onClick={() => refresh(true)}>Force Refresh</button>
<button onClick={invalidateCache}>Invalidate Cache</button>
</div>
{loading ? 'Loading...' : posts.map(post => (
<div key={post.$id}>{post.title}</div>
))}
</div>
);
}
Development Mode¶
In development mode, caching works the same way but uses browser localStorage:
const orm = new WebORM({
endpoint: 'http://localhost',
projectId: 'dev',
databaseId: 'test-db',
development: true // Uses localStorage for caching
});
const db = await orm.init(tables);
// Caching works identically
const posts = await db.table('posts').all(); // Cached in localStorage
Best Practices¶
1. Let Automatic Invalidation Work¶
// ✅ Good - Let the system handle cache invalidation
await postsTable.create(newPost);
const updatedPosts = await postsTable.all(); // Fresh data
// ❌ Avoid - Manual invalidation usually unnecessary
postsTable.setUpdated(false);
await postsTable.create(newPost);
const updatedPosts = await postsTable.all();
2. Use Cache Status for UI Feedback¶
function DataComponent({ table }) {
const [showCacheIndicator, setShowCacheIndicator] = useState(false);
const loadData = async () => {
const wasCached = table.isUpdated();
const data = await table.all();
if (wasCached) {
setShowCacheIndicator(true);
setTimeout(() => setShowCacheIndicator(false), 1000);
}
return data;
};
return (
<div>
{showCacheIndicator && <span>⚡ Loaded from cache</span>}
{/* Your data display */}
</div>
);
}
3. Force Refresh When Needed¶
function RefreshableList({ table }) {
const [data, setData] = useState([]);
const handleRefresh = async () => {
// Force fresh data from database
table.setUpdated(false);
const freshData = await table.all();
setData(freshData);
};
return (
<div>
<button onClick={handleRefresh}>🔄 Refresh</button>
{/* Your list */}
</div>
);
}
Performance Benefits¶
The caching system provides significant performance improvements:
- Reduced Database Load: Fewer queries to Appwrite
- Faster Response Times: Cached data returns instantly
- Better User Experience: Smoother interactions
- Bandwidth Savings: Less network traffic
- Cost Optimization: Fewer API calls to Appwrite
Cache Limitations¶
- Memory Usage: Cache is stored in memory (browser/Node.js)
- Single Instance: Cache is not shared between browser tabs or server instances
- TTL Bound: Cache expires after 5 minutes regardless of usage
- Query Specific: Different queries maintain separate cache entries
Troubleshooting¶
Cache Not Working¶
// Check if caching is enabled (it's always enabled)
console.log('Cache status:', table.isUpdated());
// Verify cache invalidation
await table.create(data);
console.log('After mutation:', table.isUpdated()); // Should be false
Stale Data Issues¶
// Force refresh if you suspect stale data
table.setUpdated(false);
const freshData = await table.all();