Realtime
Truss provides realtime subscriptions using PostgreSQL’s LISTEN/NOTIFY mechanism and WebSocket connections. Subscribe to table changes and receive events instantly.
How it works
Section titled “How it works”- You create a subscription for a table (e.g.,
public.messages) - Truss creates a PostgreSQL trigger on that table
- On INSERT, UPDATE, or DELETE, the trigger sends a NOTIFY to a channel
- Truss’s dedicated LISTEN connection picks up the notification
- The event is broadcast to all connected WebSocket clients
No additional services required — realtime works with just PostgreSQL. The WebSocket server is built into the Truss API.
Subscribing via dashboard
Section titled “Subscribing via dashboard”Navigate to Realtime in the sidebar. You’ll see:
- Active subscriptions and their status
- A live event log (last 200 events)
- Controls to subscribe/unsubscribe from tables
Subscribing via API
Section titled “Subscribing via API”Create a subscription
Section titled “Create a subscription”curl -X POST http://localhost:8787/api/realtime/subscribe \ -H "Content-Type: application/json" \ -d '{ "schema": "public", "table": "messages" }'This creates a trigger on public.messages that fires on INSERT, UPDATE, and DELETE.
Remove a subscription
Section titled “Remove a subscription”curl -X DELETE http://localhost:8787/api/realtime/subscribe \ -H "Content-Type: application/json" \ -d '{ "schema": "public", "table": "messages" }'List subscriptions
Section titled “List subscriptions”curl http://localhost:8787/api/realtime/subscriptionsCheck status
Section titled “Check status”curl http://localhost:8787/api/realtime/statusReturns the listener connection status, active channels, connected WebSocket clients, and event log size.
Connecting via WebSocket
Section titled “Connecting via WebSocket”Connect to the WebSocket endpoint at /realtime:
const ws = new WebSocket('ws://localhost:8787/realtime');
ws.onopen = () => { console.log('Connected to Truss realtime');};
ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log('Event:', data); // { // schema: "public", // table: "messages", // operation: "INSERT", // row: { id: 1, text: "Hello", created_at: "..." }, // old_row: null, // timestamp: "2025-01-15T10:00:00Z" // }};
ws.onclose = () => { console.log('Disconnected — will auto-reconnect');};Event format
Section titled “Event format”Each event contains:
schema— the table’s schema (usuallypublic)table— the table nameoperation—INSERT,UPDATE, orDELETErow— the new row data (null for DELETE)old_row— the previous row data (for UPDATE and DELETE)timestamp— when the event occurred
Event log
Section titled “Event log”Truss keeps the last 200 events in memory. Fetch them via:
curl http://localhost:8787/api/realtime/eventsClear the log:
curl -X POST http://localhost:8787/api/realtime/clear-logAvailable tables
Section titled “Available tables”List tables eligible for realtime subscriptions:
curl http://localhost:8787/api/realtime/tablesHow LISTEN/NOTIFY triggers work
Section titled “How LISTEN/NOTIFY triggers work”Under the hood, Truss creates a PostgreSQL trigger function for each subscription. Here is what happens step by step:
- Trigger creation — when you subscribe to
public.messages, Truss runsCREATE TRIGGER truss_rt_public_messageson the table. The trigger firesAFTER INSERT OR UPDATE OR DELETE. - NOTIFY payload — the trigger function builds a JSON payload containing
schema,table,operation,row(NEW), andold_row(OLD), then callspg_notify('truss_rt_public_messages', payload). - LISTEN connection — a dedicated
pg.Client(separate from the connection pool) runsLISTEN truss_rt_public_messages. This connection is long-lived and auto-reconnects on failure. - Broadcast — when a notification arrives, Truss deserializes the JSON and broadcasts it to every connected WebSocket client.
The payload size is limited by PostgreSQL’s 8 KB NOTIFY limit. For tables with very wide rows, only the columns that fit are included. Consider selecting specific columns in your subscription or keeping row payloads lean.
Subscription Filters
Section titled “Subscription Filters”By default, a subscription receives all INSERT, UPDATE, and DELETE events for the subscribed table. You can filter events by type when consuming them on the client side:
ws.onmessage = (event) => { const data = JSON.parse(event.data);
// Only process INSERT events if (data.operation !== 'INSERT') return;
// Only process events for a specific table if (data.table !== 'messages') return;
// Process the event handleNewMessage(data.row);};Server-side filtering (subscribing to only specific event types) is planned for a future release. Currently, all three event types are sent for every subscription, and filtering happens on the client.
Event Log Browsing
Section titled “Event Log Browsing”Truss keeps the last 200 events in an in-memory ring buffer. This is useful for debugging and monitoring without needing a separate logging service.
Fetch events
Section titled “Fetch events”curl http://localhost:8787/api/realtime/eventsReturns an array of recent events, newest first. Each entry matches the standard event format (schema, table, operation, row, old_row, timestamp).
Clear the log
Section titled “Clear the log”curl -X POST http://localhost:8787/api/realtime/clear-logThe event log is in-memory only and resets when the API server restarts.
Dashboard
Section titled “Dashboard”The Realtime panel in the dashboard shows a live-updating event log. Events appear in real time as they occur, with color-coded operation badges (green for INSERT, yellow for UPDATE, red for DELETE).
Connecting from Client Applications
Section titled “Connecting from Client Applications”Browser (vanilla JavaScript)
Section titled “Browser (vanilla JavaScript)”const ws = new WebSocket('ws://localhost:8787/realtime');
ws.onopen = () => console.log('Connected');
ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log(`${data.operation} on ${data.table}:`, data.row);};
// Reconnect on closews.onclose = () => { setTimeout(() => { // Re-create the WebSocket connection }, 1000);};React hook
Section titled “React hook”import { useEffect, useState, useRef } from 'react';
function useRealtime(table) { const [events, setEvents] = useState([]); const wsRef = useRef(null);
useEffect(() => { const ws = new WebSocket('ws://localhost:8787/realtime'); wsRef.current = ws;
ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.table === table) { setEvents((prev) => [data, ...prev].slice(0, 100)); } };
return () => ws.close(); }, [table]);
return events;}Node.js (server-side)
Section titled “Node.js (server-side)”import WebSocket from 'ws';
const ws = new WebSocket('ws://localhost:8787/realtime');
ws.on('message', (raw) => { const event = JSON.parse(raw.toString()); console.log(event);});Python
Section titled “Python”import asyncioimport websocketsimport json
async def listen(): async with websockets.connect("ws://localhost:8787/realtime") as ws: async for message in ws: event = json.loads(message) print(f"{event['operation']} on {event['table']}: {event['row']}")
asyncio.run(listen())Architecture notes
Section titled “Architecture notes”- The LISTEN connection is a dedicated
pg.Client(separate from the connection pool) - It auto-reconnects on errors
- Triggers are named
truss_rt_{schema}_{table}and send to matching channels - Subscriptions are persisted in the database
- Webhook triggers can also fire on realtime events (see Webhooks)