🚧 FastCMS is under active development — not ready for production use. APIs and features may change without notice.
FastCMS
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

ActionDescriptionExample
subscribeSubscribe 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": "*"}
unsubscribeUnsubscribe from collection{"action": "unsubscribe", "collection": "posts"}
authAuthenticate connection{"action": "auth", "token": "YOUR_JWT"}
pongRespond to server ping{"action": "pong"}

Server Messages

TypeDescription
connectedConnection established
authenticatedAuth successful
subscribedSubscription confirmed
eventData change event
pingServer heartbeat
errorError occurred

Event Types

EventWhen Triggered
record.createdNew record inserted
record.updatedRecord modified
record.deletedRecord removed
collection.createdNew collection created
collection.updatedCollection schema changed
collection.deletedCollection removed
user.joinedUser authenticated
user.leftUser 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=true

Connection 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 seconds

WebSocket Endpoint

GET ws://localhost:8000/api/v1/ws/realtime
GET ws://localhost:8000/api/v1/ws/realtime?token=JWT_TOKEN

Statistics Endpoint

GET /api/v1/stats
{
  "total_connections": 10,
  "authenticated_connections": 8,
  "unique_users": 6,
  "pubsub_backend": "redis",
  "subscriptions": {
    "conn-id-1": ["posts", "comments"]
  }
}

Troubleshooting

ProblemSolution
Connection closes immediatelyCheck if JWT token is valid
No messages receivedEnsure you sent a subscribe action
Connection drops frequentlyAlways respond to ping with pong
Events not matching filterVerify 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

  1. Use Filters — Subscribe with filters to reduce unnecessary events
  2. Respond to Pings — Always respond to ping with pong
  3. Handle Reconnection — Implement exponential backoff
  4. Batch UI Updates — Debounce UI updates for high-frequency events
  5. Scale with Redis — For production multi-server deployments, enable Redis

On this page