subscriptions
rest

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
}
FieldDescription
typeINSERT, UPDATE, or DELETE
schemaDatabase schema
tableTable name
dataRow data with real column names. UPDATE includes old and new objects
timestampEvent 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

SSEWebSocket
ProtocolHTTP (one-way server → client)Bidirectional
Auto-reconnectBuilt into browserManual
Proxy-friendlyYes (standard HTTP)Needs upgrade support
Binary dataNoYes
Best forBrowser dashboards, simple consumersTwo-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 SubscriptionsCDC Watcher
TransportSSE / WebSocket (HTTP)NATS JetStream (message queue)
Best forFrontend dashboards, admin UIsBackend service-to-service
ConsumerBrowser or HTTP clientAny NATS consumer (Java, Node, Go, etc.)
DeliveryAt-most-once (connection-based)At-least-once (durable consumer)
ReplayNoYes (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.