Real-time
Real-time Overview
WebSocket-based real-time engine with filtered subscriptions, presence tracking, and Redis-backed horizontal scaling.
Real-time Features
FastCMS provides a production-ready real-time engine using WebSockets and a Pub/Sub backend.
Key Capabilities
- Horizontal Scaling — Run multiple server instances behind a load balancer
- Low Latency — WebSocket connections provide instant event delivery
- Filtered Subscriptions — Subscribe to specific data with filters (live queries)
- Presence Tracking — Know when users join and leave
- Automatic Fallback — Works in single-server mode without Redis
Quick Start
1. Connect via WebSocket
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/realtime');
ws.onopen = () => {
ws.send(JSON.stringify({
action: 'subscribe',
collection: 'posts'
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};2. Handle Events
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.type) {
case 'connected':
console.log('Connection ID:', message.data.connection_id);
break;
case 'subscribed':
console.log('Subscribed to:', message.data.collection);
break;
case 'event':
const { type, collection, data } = message.data;
console.log(`${type} on ${collection}:`, data);
break;
case 'ping':
ws.send(JSON.stringify({ action: 'pong' }));
break;
}
};Client Actions
| Action | Description | Example |
|---|---|---|
subscribe | Subscribe to a collection | {"action": "subscribe", "collection": "posts"} |
subscribe (filtered) | Subscribe with filter | {"action": "subscribe", "collection": "posts", "filter": {"published": true}} |
subscribe (global) | Subscribe to all events | {"action": "subscribe", "collection": "*"} |
unsubscribe | Unsubscribe from collection | {"action": "unsubscribe", "collection": "posts"} |
auth | Authenticate connection | {"action": "auth", "token": "YOUR_JWT"} |
pong | Respond to server ping | {"action": "pong"} |
Server Messages
| Type | Description |
|---|---|
connected | Connection established |
authenticated | Auth successful |
subscribed | Subscription confirmed |
event | Data change event |
ping | Server heartbeat |
error | Error occurred |
Event Types
| Event | When Triggered |
|---|---|
record.created | New record inserted |
record.updated | Record modified |
record.deleted | Record removed |
collection.created | New collection created |
collection.updated | Collection schema changed |
collection.deleted | Collection removed |
user.joined | User authenticated |
user.left | User disconnected |
Authentication
Option 1: Token in URL
const ws = new WebSocket(`ws://localhost:8000/api/v1/ws/realtime?token=${token}`);Option 2: Authenticate After Connect
const ws = new WebSocket('ws://localhost:8000/api/v1/ws/realtime');
ws.onopen = () => {
ws.send(JSON.stringify({ action: 'auth', token: 'YOUR_JWT_TOKEN' }));
};Filtered Subscriptions
Subscribe to receive only events matching specific criteria:
// Only published posts
ws.send(JSON.stringify({
action: 'subscribe',
collection: 'posts',
filter: { published: true }
}));
// Comments for a specific post
ws.send(JSON.stringify({
action: 'subscribe',
collection: 'comments',
filter: { post_id: 'abc123' }
}));React Hook Example
import { useState, useEffect, useRef, useCallback } from 'react';
export function useRealtime(baseUrl, token = null) {
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef(null);
const listenersRef = useRef(new Map());
useEffect(() => {
const wsUrl = baseUrl.replace('http', 'ws');
const url = token
? `${wsUrl}/api/v1/ws/realtime?token=${token}`
: `${wsUrl}/api/v1/ws/realtime`;
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => setIsConnected(true);
ws.onclose = () => setIsConnected(false);
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'ping') {
ws.send(JSON.stringify({ action: 'pong' }));
return;
}
if (message.type === 'event') {
const key = `${message.data.collection}:${message.data.type}`;
listenersRef.current.get(key)?.forEach(cb => cb(message.data));
}
};
return () => ws.close();
}, [baseUrl, token]);
const subscribe = useCallback((collection, filter = null) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify({ action: 'subscribe', collection, filter }));
}
}, []);
const onEvent = useCallback((collection, eventType, callback) => {
const key = `${collection}:${eventType}`;
if (!listenersRef.current.has(key)) {
listenersRef.current.set(key, []);
}
listenersRef.current.get(key).push(callback);
return () => {
const listeners = listenersRef.current.get(key);
const index = listeners?.indexOf(callback);
if (index > -1) listeners.splice(index, 1);
};
}, []);
return { isConnected, subscribe, onEvent };
}Configuration
# Redis for multi-server deployments
REDIS_URL=redis://localhost:6379/0
REDIS_ENABLED=trueConnection manager timeouts (in app/core/websocket_manager.py):
HEARTBEAT_INTERVAL = 30 # Server sends ping every 30 seconds
HEARTBEAT_TIMEOUT = 90 # Connection considered dead after 90 seconds
CLEANUP_INTERVAL = 60 # Stale connection cleanup runs every 60 secondsWebSocket Endpoint
GET ws://localhost:8000/api/v1/ws/realtime
GET ws://localhost:8000/api/v1/ws/realtime?token=JWT_TOKENStatistics Endpoint
GET /api/v1/stats{
"total_connections": 10,
"authenticated_connections": 8,
"unique_users": 6,
"pubsub_backend": "redis",
"subscriptions": {
"conn-id-1": ["posts", "comments"]
}
}Troubleshooting
| Problem | Solution |
|---|---|
| Connection closes immediately | Check if JWT token is valid |
| No messages received | Ensure you sent a subscribe action |
| Connection drops frequently | Always respond to ping with pong |
| Events not matching filter | Verify filter keys match record fields exactly |
Testing with wscat
# Install wscat
npm install -g wscat
# Connect
wscat -c "ws://localhost:8000/api/v1/ws/realtime"
# Then subscribe
{"action": "subscribe", "collection": "posts"}Performance Tips
- Use Filters — Subscribe with filters to reduce unnecessary events
- Respond to Pings — Always respond to
pingwithpong - Handle Reconnection — Implement exponential backoff
- Batch UI Updates — Debounce UI updates for high-frequency events
- Scale with Redis — For production multi-server deployments, enable Redis
CSV Import / Export
Import and export collection data in CSV format — compatible with Excel, Google Sheets, and data migration workflows.
Webhooks
Subscribe to collection events and receive HTTP callbacks when records are created, updated, or deleted. Includes HMAC signature verification and automatic retry logic.