Real-time Subscriptions
Excalibase REST can stream INSERT, UPDATE, and DELETE events from your PostgreSQL database to any client via Server-Sent Events (SSE) or WebSocket — powered by CDC and NATS JetStream under the hood.
[PostgreSQL WAL]
↓ logical replication
excalibase-rest (CDCService)
↓ NATS JetStream
↓
┌────────────────────┐
│ SSE endpoint │ → GET /api/v1/{table}/changes (text/event-stream)
│ WebSocket endpoint│ → ws://host/ws/{table}/changes
└────────────────────┘
↓
Browser / mobile / backend consumer
SSE (Server-Sent Events)
Endpoint
GET /api/v1/{table}/changes
Accept: text/event-stream
JavaScript client
const source = new EventSource('http://localhost:20000/api/v1/orders/changes')
source.onmessage = (event) => {
const change = JSON.parse(event.data)
console.log(change.type, change.data)
// INSERT { id: 1, product: "keyboard", qty: 2 }
// UPDATE { old: { status: "new" }, new: { status: "shipped" } }
// DELETE { id: 1 }
}
source.onerror = () => {
console.log('Connection lost, reconnecting...')
// EventSource reconnects automatically
}
SSE reconnects automatically on network failure — the browser handles this natively.
curl
curl -N -H "Accept: text/event-stream" \
http://localhost:20000/api/v1/orders/changes
WebSocket
Endpoint
ws://host/ws/{table}/changes
JavaScript client
const ws = new WebSocket('ws://localhost:20000/ws/orders/changes')
ws.onmessage = (event) => {
const change = JSON.parse(event.data)
console.log(change.type, change.data)
}
ws.onclose = () => {
// Implement your own reconnect logic
setTimeout(() => {
// reconnect...
}, 1000)
}
Event Format
Both SSE and WebSocket deliver the same JSON payload:
{
"type": "INSERT",
"schema": "public",
"table": "orders",
"data": {
"id": 1,
"product": "keyboard",
"qty": 2
},
"timestamp": 1742056200000
}
| Field | Description |
|---|---|
type | INSERT, UPDATE, or DELETE |
schema | Database schema |
table | Table name |
data | Row data with real column names. UPDATE includes old and new objects |
timestamp | Event time (epoch ms) |
NATS Configuration
CDC requires NATS JetStream. Add these to your application.yaml:
app:
nats:
enabled: true
url: nats://localhost:4222
stream-name: cdc-stream
Or via environment variables:
APP_NATS_ENABLED=true
APP_NATS_URL=nats://localhost:4222
APP_NATS_STREAM_NAME=cdc-stream
NATS must be running with JetStream enabled. The provided docker-compose.yml handles this.
PostgreSQL REPLICA IDENTITY
By default, DELETE events include only the primary key. For full row data on DELETE and old values on UPDATE, set:
ALTER TABLE orders REPLICA IDENTITY FULL;
See CDC Watcher — PostgreSQL Setup for details on REPLICA IDENTITY modes.
SSE vs WebSocket
| SSE | WebSocket | |
|---|---|---|
| Protocol | HTTP (one-way server → client) | Bidirectional |
| Auto-reconnect | Built into browser | Manual |
| Proxy-friendly | Yes (standard HTTP) | Needs upgrade support |
| Binary data | No | Yes |
| Best for | Browser dashboards, simple consumers | Two-way communication, non-browser clients |
For most use cases (dashboards, live feeds, admin panels), SSE is simpler and recommended. Use WebSocket when you need bidirectional communication or are connecting from a non-browser client that doesn't support SSE.
Difference from CDC Watcher
| REST Subscriptions | CDC Watcher | |
|---|---|---|
| Transport | SSE / WebSocket (HTTP) | NATS JetStream (message queue) |
| Best for | Frontend dashboards, admin UIs | Backend service-to-service |
| Consumer | Browser or HTTP client | Any NATS consumer (Java, Node, Go, etc.) |
| Delivery | At-most-once (connection-based) | At-least-once (durable consumer) |
| Replay | No | Yes (NATS replay from offset) |
Both features are powered by the same CDC pipeline. REST Subscriptions expose it over HTTP for frontend convenience; CDC Watcher exposes the raw NATS stream for backend durability.