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
- 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
Setup
No additional services required — realtime works with just PostgreSQL. The WebSocket server is built into the Truss API.
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
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
curl -X DELETE http://localhost:8787/api/realtime/subscribe \ -H "Content-Type: application/json" \ -d '{ "schema": "public", "table": "messages" }'List subscriptions
curl http://localhost:8787/api/realtime/subscriptionsCheck status
curl http://localhost:8787/api/realtime/statusReturns the listener connection status, active channels, connected WebSocket clients, and event log size.
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
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
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
List tables eligible for realtime subscriptions:
curl http://localhost:8787/api/realtime/tablesHow 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
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
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
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
curl -X POST http://localhost:8787/api/realtime/clear-logThe event log is in-memory only and resets when the API server restarts.
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
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
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)
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
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
- 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)