Live Order Dashboard
Subscribe to a D1 table and watch changes arrive in real time — no sdk.socket.emit() needed. When the server runs a SQL UPDATE, ChangeTracker detects it and broadcasts the row to all subscribers automatically.
Demonstrates: D1 table subscriptions, channel('orders') (no slash = ChangeTracker), on('INSERT'), on('UPDATE')
Source: examples/socket-hub/src/examples/LiveOrderDashboard/
How it works
Section titled “How it works”Server runs: UPDATE orders SET status = 'shipped' WHERE id = '123' → ChangeTracker polls D1 every 200ms → Detects the mutation → Broadcasts UPDATE event to all subscribers on channel('orders') → Dashboard UI updates in real time — no manual emit requiredThe key difference from pub/sub: the channel name has no slash. This activates ChangeTracker for the orders table.
Client code
Section titled “Client code”import { useAerostack } from '@aerostack/react'
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'
interface Order { id: string customer_name: string product: string amount: number status: OrderStatus created_at: number}
const STATUS_COLORS: Record<OrderStatus, string> = { pending: 'bg-yellow-500/10 text-yellow-400', processing: 'bg-blue-500/10 text-blue-400', shipped: 'bg-purple-500/10 text-purple-400', delivered: 'bg-green-500/10 text-green-400', cancelled: 'bg-red-500/10 text-red-400',}
export function LiveOrderDashboard() { const { realtime } = useAerostack() const [orders, setOrders] = useState<Order[]>([]) const [recentUpdate, setRecentUpdate] = useState<string | null>(null)
useEffect(() => { // Load initial data fetch('/api/examples/orders') .then(r => r.json()) .then(data => setOrders(data.orders ?? []))
// Subscribe to live changes // Note: no slash in topic name → ChangeTracker activates for 'orders' table const channel = realtime.channel('orders')
channel .on('INSERT', ({ data }) => { setOrders(prev => [data, ...prev]) setRecentUpdate(`New order: ${data.id}`) setTimeout(() => setRecentUpdate(null), 3000) }) .on('UPDATE', ({ data }) => { setOrders(prev => prev.map(o => o.id === data.id ? { ...o, ...data } : o)) setRecentUpdate(`Order ${data.id} → ${data.status}`) setTimeout(() => setRecentUpdate(null), 3000) }) .subscribe()
return () => channel.unsubscribe() }, [realtime])
const updateOrderStatus = async (orderId: string, status: OrderStatus) => { await fetch('/api/examples/orders/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId, status }), }) // No need to update local state — the subscription will receive the UPDATE event }
return ( <div className="p-6"> <div className="flex items-center justify-between mb-6"> <h1 className="text-2xl font-bold text-white">Orders</h1> {recentUpdate && ( <div className="text-sm text-green-400 bg-green-500/10 px-4 py-2 rounded-full"> ↻ {recentUpdate} </div> )} </div>
<div className="space-y-3"> {orders.map(order => ( <div key={order.id} className="flex items-center justify-between p-4 rounded-xl border border-gray-800 bg-gray-900/50" > <div> <div className="font-medium text-white">{order.customer_name}</div> <div className="text-sm text-gray-400">{order.product} · ${order.amount}</div> </div> <div className="flex items-center gap-3"> <span className={`text-xs px-3 py-1 rounded-full font-medium ${STATUS_COLORS[order.status]}`}> {order.status} </span> <select value={order.status} onChange={e => updateOrderStatus(order.id, e.target.value as OrderStatus)} className="text-xs bg-gray-800 border border-gray-700 rounded-lg px-2 py-1 text-gray-300" > <option value="pending">pending</option> <option value="processing">processing</option> <option value="shipped">shipped</option> <option value="delivered">delivered</option> <option value="cancelled">cancelled</option> </select> </div> </div> ))} </div> </div> )}Server code
Section titled “Server code”import { Hono } from 'hono'import { sdk } from '@aerostack/sdk'
const app = new Hono()
// Setup: create orders tableapp.post('/setup', async (c) => { await sdk.db.exec(` CREATE TABLE IF NOT EXISTS orders ( id TEXT PRIMARY KEY, customer_name TEXT NOT NULL, product TEXT NOT NULL, amount REAL NOT NULL, status TEXT DEFAULT 'pending', created_at INTEGER NOT NULL ) `) return c.json({ ok: true })})
// List ordersapp.get('/examples/orders', async (c) => { const { results } = await sdk.db.query( 'SELECT * FROM orders ORDER BY created_at DESC LIMIT 50' ) return c.json({ orders: results })})
// Update order status — ChangeTracker broadcasts the UPDATE automaticallyapp.post('/examples/orders/update', async (c) => { const { orderId, status } = await c.req.json()
await sdk.db.query( 'UPDATE orders SET status = ? WHERE id = ?', [status, orderId] )
// No sdk.socket.emit needed — ChangeTracker handles the broadcast return c.json({ ok: true })})
export default app