Real-time Data Updates
In this tutorial, you will learn how to handle real-time data updates in dcupl applications. You will build a live dashboard that reflects data changes instantly, using partial updates for performance and event subscriptions for reactivity.
Time required: 15-20 minutes
What you will build: A live inventory dashboard that updates in real-time as stock levels change, prices update, and new products are added.
Prerequisites
Before starting, make sure you have:
- Completed the Build Your First dcupl Application tutorial
- Node.js 18 or higher installed
- Understanding of async/await patterns
Why Real-time Updates Matter
Modern applications need to reflect data changes immediately:
- Inventory systems - Stock levels change constantly
- Pricing dashboards - Prices update in real-time
- Collaborative tools - Multiple users edit simultaneously
- Live feeds - New content appears automatically
dcupl provides three key mechanisms for handling this:
- Partial updates - Update only changed data (10-100x faster than full reload)
- Event subscriptions - React to data changes automatically
- List auto-updates - Keep query results synchronized
Step 1: Set Up the Project
Create a new project:
mkdir realtime-dashboard
cd realtime-dashboard
npm init -y
npm install @dcupl/coreCreate the main application file:
import { Dcupl } from '@dcupl/core';
async function main() {
const dcupl = new Dcupl();
// We will build the real-time dashboard here
}
main();Step 2: Define the Inventory Model
Create a model for tracking product inventory:
import { Dcupl } from '@dcupl/core';
async function main() {
const dcupl = new Dcupl();
// Define Inventory model
dcupl.models.set({
key: 'Inventory',
properties: [
{ key: 'sku', type: 'string' },
{ key: 'name', type: 'string' },
{ key: 'category', type: 'string' },
{ key: 'price', type: 'float' },
{ key: 'stock', type: 'int' },
{ key: 'lowStockThreshold', type: 'int' },
{ key: 'lastUpdated', type: 'string' },
],
});
console.log('Inventory model defined');
}
main();Step 3: Load Initial Data
Add initial inventory data:
// ... after dcupl.models.set() ...
// Load initial inventory
dcupl.data.set(
[
{
key: 'inv1',
sku: 'LAPTOP-001',
name: 'MacBook Pro 14"',
category: 'Electronics',
price: 1999.99,
stock: 25,
lowStockThreshold: 10,
lastUpdated: new Date().toISOString(),
},
{
key: 'inv2',
sku: 'LAPTOP-002',
name: 'Dell XPS 15',
category: 'Electronics',
price: 1499.99,
stock: 18,
lowStockThreshold: 8,
lastUpdated: new Date().toISOString(),
},
{
key: 'inv3',
sku: 'PHONE-001',
name: 'iPhone 15 Pro',
category: 'Electronics',
price: 1199.99,
stock: 45,
lowStockThreshold: 20,
lastUpdated: new Date().toISOString(),
},
{
key: 'inv4',
sku: 'CHAIR-001',
name: 'Ergonomic Office Chair',
category: 'Furniture',
price: 399.99,
stock: 12,
lowStockThreshold: 5,
lastUpdated: new Date().toISOString(),
},
{
key: 'inv5',
sku: 'DESK-001',
name: 'Standing Desk',
category: 'Furniture',
price: 599.99,
stock: 8,
lowStockThreshold: 5,
lastUpdated: new Date().toISOString(),
},
{
key: 'inv6',
sku: 'HEADPHONES-001',
name: 'Sony WH-1000XM5',
category: 'Electronics',
price: 349.99,
stock: 32,
lowStockThreshold: 15,
lastUpdated: new Date().toISOString(),
},
],
{ model: 'Inventory' }
);
await dcupl.init();
console.log('Initial inventory loaded');Step 4: Create the Inventory List
Create a list to query inventory data:
// ... after dcupl.init() ...
// Create inventory list
const inventoryList = dcupl.lists.create({
modelKey: 'Inventory',
});
// Display dashboard
function displayDashboard() {
console.clear();
console.log('===========================================');
console.log(' INVENTORY DASHBOARD');
console.log('===========================================\n');
const items = inventoryList.catalog.query.items({
sort: { attributes: ['stock'], order: ['asc'] },
});
console.log('SKU | Name | Stock | Price | Status');
console.log('---------------|-------------------------|-------|----------|--------');
items.forEach((item) => {
const status = item.stock <= item.lowStockThreshold ? 'LOW' : 'OK';
const statusColor = status === 'LOW' ? '!!' : ' ';
console.log(
`${item.sku.padEnd(14)} | ` +
`${item.name.slice(0, 23).padEnd(23)} | ` +
`${String(item.stock).padStart(5)} | ` +
`${item.price.toFixed(2).padStart(7)} | ` +
`${statusColor}${status}`
);
});
// Summary stats
const totalStock = items.reduce((sum, i) => sum + i.stock, 0);
const lowStockCount = items.filter((i) => i.stock <= i.lowStockThreshold).length;
const totalValue = items.reduce((sum, i) => sum + i.stock * i.price, 0);
console.log('\n-------------------------------------------');
console.log(`Total Items: ${items.length} | Total Stock: ${totalStock} | Low Stock Alerts: ${lowStockCount}`);
console.log(`Total Inventory Value: ${totalValue.toLocaleString()}`);
console.log('-------------------------------------------\n');
}
displayDashboard();Step 5: Implement Partial Updates with data.update()
The data.update() method updates existing items without replacing all data. This is much faster than data.set():
// Simulate a sale - reduce stock for one item
async function processSale(itemKey: string, quantity: number) {
const item = inventoryList.catalog.query.one({ itemKey });
if (!item) {
console.log(`Item ${itemKey} not found`);
return;
}
const newStock = Math.max(0, item.stock - quantity);
// Use data.update() for partial update
dcupl.data.update(
[
{
key: itemKey,
stock: newStock,
lastUpdated: new Date().toISOString(),
},
],
{ model: 'Inventory' }
);
// Process the update
await dcupl.update();
console.log(`Sale processed: ${item.name} - Sold ${quantity}, New stock: ${newStock}`);
}
// Test a sale
console.log('\n--- Processing Sale ---');
await processSale('inv1', 3);
displayDashboard();Step 6: Subscribe to Data Changes with on()
Use the on() method to react to data changes:
// Subscribe to dcupl events
const unsubscribe = dcupl.on((event) => {
console.log(`\n[Event] ${event.type}`);
if (event.type === 'dcupl_updated_manually') {
console.log('Data was updated - refreshing dashboard...');
displayDashboard();
}
});
// Now updates will trigger the callback
console.log('\n--- Subscribing to changes ---');
console.log('Dashboard will auto-refresh on data changes\n');Step 7: Implement Upsert for New Items
Use data.upsert() to add new items or update existing ones:
// Add new inventory or update existing
async function upsertInventory(item: any) {
dcupl.data.upsert([item], { model: 'Inventory' });
await dcupl.update();
console.log(`Upserted: ${item.name}`);
}
// Add a new product
console.log('\n--- Adding New Product ---');
await upsertInventory({
key: 'inv7',
sku: 'MONITOR-001',
name: '4K Ultra Monitor',
category: 'Electronics',
price: 699.99,
stock: 15,
lowStockThreshold: 5,
lastUpdated: new Date().toISOString(),
});Step 8: Implement Batch Updates
Update multiple items at once for better performance:
// Batch price update (e.g., 10% discount on all electronics)
async function applyDiscount(category: string, discountPercent: number) {
const items = inventoryList.catalog.query.items();
const categoryItems = items.filter((i) => i.category === category);
const updates = categoryItems.map((item) => ({
key: item.key,
price: Number((item.price * (1 - discountPercent / 100)).toFixed(2)),
lastUpdated: new Date().toISOString(),
}));
if (updates.length > 0) {
dcupl.data.update(updates, { model: 'Inventory' });
await dcupl.update();
console.log(`Applied ${discountPercent}% discount to ${updates.length} ${category} items`);
}
}
console.log('\n--- Applying Batch Discount ---');
await applyDiscount('Electronics', 10);Step 9: Handle Stock Replenishment
Simulate receiving new inventory:
// Replenish stock for low items
async function replenishLowStock() {
const items = inventoryList.catalog.query.items();
const lowStockItems = items.filter((i) => i.stock <= i.lowStockThreshold);
if (lowStockItems.length === 0) {
console.log('No items need replenishment');
return;
}
const updates = lowStockItems.map((item) => ({
key: item.key,
stock: item.stock + 50, // Add 50 units
lastUpdated: new Date().toISOString(),
}));
dcupl.data.update(updates, { model: 'Inventory' });
await dcupl.update();
console.log(`Replenished ${updates.length} low-stock items (+50 each)`);
}
console.log('\n--- Checking for Low Stock ---');
await replenishLowStock();Step 10: Implement Real-time Simulation
Create a simulation that mimics real-world inventory changes:
// Simulate real-time inventory changes
function simulateRealTimeUpdates() {
const operations = [
async () => {
// Random sale
const items = inventoryList.catalog.query.items();
const randomItem = items[Math.floor(Math.random() * items.length)];
const qty = Math.floor(Math.random() * 5) + 1;
await processSale(randomItem.key, qty);
},
async () => {
// Random price change
const items = inventoryList.catalog.query.items();
const randomItem = items[Math.floor(Math.random() * items.length)];
const priceChange = (Math.random() - 0.5) * 50; // +/- $25
dcupl.data.update(
[
{
key: randomItem.key,
price: Number((randomItem.price + priceChange).toFixed(2)),
lastUpdated: new Date().toISOString(),
},
],
{ model: 'Inventory' }
);
await dcupl.update();
console.log(`Price updated: ${randomItem.name}`);
},
async () => {
// Stock arrival
const items = inventoryList.catalog.query.items();
const randomItem = items[Math.floor(Math.random() * items.length)];
const arrival = Math.floor(Math.random() * 20) + 5;
dcupl.data.update(
[
{
key: randomItem.key,
stock: randomItem.stock + arrival,
lastUpdated: new Date().toISOString(),
},
],
{ model: 'Inventory' }
);
await dcupl.update();
console.log(`Stock arrival: ${randomItem.name} +${arrival}`);
},
];
// Run random operation every 2 seconds
const interval = setInterval(async () => {
const operation = operations[Math.floor(Math.random() * operations.length)];
await operation();
}, 2000);
return () => clearInterval(interval);
}
// Start simulation
console.log('\n--- Starting Real-time Simulation ---');
console.log('Updates will occur every 2 seconds...\n');
const stopSimulation = simulateRealTimeUpdates();
// Stop after 10 seconds
setTimeout(() => {
stopSimulation();
console.log('\n--- Simulation Stopped ---');
// Cleanup subscription
unsubscribe();
console.log('Unsubscribed from events');
}, 10000);Step 11: Implement Remove and Reset
Handle item removal and full data reset:
// Remove discontinued items
async function removeItem(itemKey: string) {
dcupl.data.remove([{ key: itemKey }], { model: 'Inventory' });
await dcupl.update();
console.log(`Removed item: ${itemKey}`);
}
// Reset all inventory (e.g., end of year)
async function resetInventory(newData: any[]) {
// data.set() replaces ALL data - use sparingly
dcupl.data.set(newData, { model: 'Inventory' });
await dcupl.update();
console.log('Inventory completely reset');
}Step 12: Enable Auto-updating Lists
Configure lists to automatically update when data changes:
// Option 1: Enable auto-update for a specific list
const autoList = dcupl.lists.create({
modelKey: 'Inventory',
autoUpdate: true, // This list automatically refreshes on data changes
});
// Option 2: Enable globally for all lists during dcupl creation
const dcuplWithAutoUpdate = new Dcupl({
performance: {
autoUpdateLists: {
enabled: true, // All lists auto-update by default
},
},
});
// Option 3: Override auto-update behavior per update() call
await dcupl.update({ autoUpdateLists: true }); // Force update all lists
await dcupl.update({ autoUpdateLists: false }); // Skip auto-update for all listsComplete Code
Here is the complete real-time dashboard:
import { Dcupl } from '@dcupl/core';
async function main() {
const dcupl = new Dcupl();
// Define model
dcupl.models.set({
key: 'Inventory',
properties: [
{ key: 'sku', type: 'string' },
{ key: 'name', type: 'string' },
{ key: 'category', type: 'string' },
{ key: 'price', type: 'float' },
{ key: 'stock', type: 'int' },
{ key: 'lowStockThreshold', type: 'int' },
{ key: 'lastUpdated', type: 'string' },
],
});
// Load data
dcupl.data.set(
[
{ key: 'inv1', sku: 'LAPTOP-001', name: 'MacBook Pro 14"', category: 'Electronics', price: 1999.99, stock: 25, lowStockThreshold: 10, lastUpdated: new Date().toISOString() },
{ key: 'inv2', sku: 'LAPTOP-002', name: 'Dell XPS 15', category: 'Electronics', price: 1499.99, stock: 18, lowStockThreshold: 8, lastUpdated: new Date().toISOString() },
{ key: 'inv3', sku: 'PHONE-001', name: 'iPhone 15 Pro', category: 'Electronics', price: 1199.99, stock: 45, lowStockThreshold: 20, lastUpdated: new Date().toISOString() },
{ key: 'inv4', sku: 'CHAIR-001', name: 'Ergonomic Office Chair', category: 'Furniture', price: 399.99, stock: 12, lowStockThreshold: 5, lastUpdated: new Date().toISOString() },
{ key: 'inv5', sku: 'DESK-001', name: 'Standing Desk', category: 'Furniture', price: 599.99, stock: 8, lowStockThreshold: 5, lastUpdated: new Date().toISOString() },
],
{ model: 'Inventory' }
);
await dcupl.init();
const inventoryList = dcupl.lists.create({ modelKey: 'Inventory' });
// Display function
function displayDashboard() {
console.log('\n=== INVENTORY DASHBOARD ===\n');
const items = inventoryList.catalog.query.items({ sort: { attributes: ['stock'], order: ['asc'] } });
items.forEach((item) => {
const status = item.stock <= item.lowStockThreshold ? '[LOW]' : '[OK] ';
console.log(`${status} ${item.name}: ${item.stock} units @ ${item.price}`);
});
console.log(`\nTotal: ${items.length} products, ${items.reduce((s, i) => s + i.stock, 0)} units`);
}
// Subscribe to updates
const unsubscribe = dcupl.on((event) => {
if (event.type === 'dcupl_updated_manually') {
displayDashboard();
}
});
// Initial display
displayDashboard();
// Demonstrate updates
console.log('\n--- Simulating sale of 5 MacBooks ---');
dcupl.data.update([{ key: 'inv1', stock: 20, lastUpdated: new Date().toISOString() }], { model: 'Inventory' });
await dcupl.update();
console.log('\n--- Adding new product ---');
dcupl.data.upsert([{ key: 'inv6', sku: 'MONITOR-001', name: '4K Monitor', category: 'Electronics', price: 699.99, stock: 15, lowStockThreshold: 5, lastUpdated: new Date().toISOString() }], { model: 'Inventory' });
await dcupl.update();
console.log('\n--- Batch price update (10% off Electronics) ---');
const electronics = inventoryList.catalog.query.items().filter((i) => i.category === 'Electronics');
const priceUpdates = electronics.map((i) => ({ key: i.key, price: Number((i.price * 0.9).toFixed(2)) }));
dcupl.data.update(priceUpdates, { model: 'Inventory' });
await dcupl.update();
// Cleanup
unsubscribe();
}
main();What You Learned
In this tutorial, you learned how to:
- Use
data.update()for partial updates that only modify changed fields - Use
data.upsert()to insert or update items in a single operation - Subscribe to dcupl events with
on()for reactive updates - Process batch updates efficiently
- Handle
data.remove()for deleting items - Use
data.set()for complete data replacement (use sparingly) - Configure auto-updating lists for automatic synchronization
Data Methods Summary
| Method | Purpose | Performance | Use Case |
|---|---|---|---|
data.update() |
Update existing items | Fast (partial) | Stock changes, price updates |
data.upsert() |
Insert or update | Fast | New items or sync from external source |
data.set() |
Replace all data | Slow (full reload) | Initial load, complete refresh |
data.remove() |
Delete items | Fast | Discontinued products |
data.reset() |
Clear all data | Fast | Clear model before reload |
Update Workflow
// 1. Make data changes (staged, not applied yet)
dcupl.data.update([...], { model: 'MyModel' });
dcupl.data.upsert([...], { model: 'MyModel' });
dcupl.data.remove([...], { model: 'MyModel' });
// 2. Apply all changes at once
await dcupl.update();
// 3. Event subscribers are notified
// 4. Auto-updating lists refresh automaticallyNext Steps
- Implementing Faceted Search - Build search with live facet counts
- Building with React - Integrate real-time updates with React
- Building with Vue - Integrate real-time updates with Vue
Common Issues
Updates not reflecting in UI
Make sure you call await dcupl.update() after making data changes. Data modifications are staged until you call update().
Slow performance with large updates
Use partial updates (data.update()) instead of full replacement (data.set()) whenever possible. For batch updates, combine multiple changes into a single data.update() call.
Event handler called multiple times
Each call to on() creates a new subscription. Store the unsubscribe function and call it when the component unmounts or before creating a new subscription.
// Store reference
const unsubscribe = dcupl.on(callback);
// Later: cleanup
unsubscribe();List not updating automatically
Enable auto-update when creating the list:
const list = dcupl.lists.create({
modelKey: 'MyModel',
autoUpdate: true,
});Or manually call list.update() after dcupl.update().