A real-time change stream broadcaster for MongoDB, supporting multiple delivery channels (WebSocket, Redis, HTTP, etc.) with extensible architecture.
Read the blog post on the implementation here.
- 📡 Listen to MongoDB change streams
- 🚀 Built-in channels: WebSocket, Redis, HTTP, and Database Logging
- 🔌 Extensible architecture for custom channels
- ⚡ Async-first implementation
- 🛠️ Configurable pipelines and filtering
WebSocketChannel: Added a ping/pong monitor to detect dead clients (
PING_INTERVAL = 60s), plus configurabletimeoutanddisconnect_on_timeoutto automatically drop stale connections. The channel now supports anauthenticatecoroutine (return(bool, client_id)) to accept and assign client IDs, and replaces existing connections for the same client ID (closes old socket and cancels its ping task). Sending uses safe JSON serialization and includes robust disconnect handling on send failures.MongoChangeBroadcaster: Added a
custom_fnhook that can transformChangeEventobjects before delivery. Collection watchers now run with an exponential backoff retry (viatenacity) for resiliency, and the broadcaster validates the Mongo URI before connecting. You can filter by field-level changes withfields_to_watch, and extract recipients using dot-notation paths (e.g.owner.id) for targeted delivery. Start/stop flows and error logging were also improved.
pip install mongo-broadcaster
# Optional dependencies for specific channels:pip install mongo_broadcaster[fastapi] # WebSocketpip install mongo_broadcaster[redis] # Redis Pub/Sub supportfrommongo_broadcasterimport (
MongoChangeBroadcaster,
BroadcasterConfig,
CollectionConfig
)
frommongo_broadcaster.channelsimportWebSocketChannel# Initialize with MongoDB connectionconfig=BroadcasterConfig(
mongo_uri="mongodb://localhost:27017",
collections=[
CollectionConfig(
collection_name="users",
fields_to_watch=["name", "email"],
recipient_identifier="fullDocument._id"
)
]
)
broadcaster=MongoChangeBroadcaster(config)
broadcaster.add_channel(WebSocketChannel())
# Start listening (typically in your app startup)awaitbroadcaster.start()| Channel | Description | Ideal For |
|---|---|---|
WebSocketChannel | Real-time browser updates | Live dashboards |
RedisPubSubChannel | Pub/Sub messaging | Microservices |
HTTPCallbackChannel | Webhook notifications | Third-party integrations |
DatabaseChannel | Persistent change logging | Audit trails |
Implement your own channel by subclassing BaseChannel:
frommongo_broadcaster.channels.baseimportBaseChannelfromtypingimportAny, DictclassCustomMQTTChannel(BaseChannel):
def__init__(self, broker_url: str):
self.broker_url=broker_urlself.client=Noneasyncdefconnect(self):
"""Initialize your connection"""self.client=awaitsetup_mqtt_client(self.broker_url)
asyncdefsend(self, recipient: str, message: Dict[str, Any]):
"""Send to specific recipient"""awaitself.client.publish(f"changes/{recipient}", message)
asyncdefbroadcast(self, message: Dict[str, Any]):
"""Send to all subscribers"""awaitself.client.publish("changes/all", message)
asyncdefdisconnect(self):
"""Clean up resources"""awaitself.client.disconnect()
# Usage:broadcaster.add_channel(CustomMQTTChannel("mqtt://localhost"))CollectionConfig(
collection_name: str,
database_name: Optional[str] =None,
# Fields to include in change eventsfields_to_watch: List[str] = [],
# Dot-notation path to identify recipients (e.g., "fullDocument._id")recipient_identifier: Optional[str] =None,
# MongoDB change stream optionschange_stream_config: ChangeStreamConfig=ChangeStreamConfig()
)fromfastapiimportFastAPI, WebSocketapp=FastAPI()
ws_channel=WebSocketChannel()
@app.websocket("/ws/{client_id}")asyncdefwebsocket_endpoint(websocket: WebSocket, client_id: str):
awaitws_channel.connect(client_id, websocket)
try:
whileTrue:
awaitwebsocket.receive_text()
exceptWebSocketDisconnect:
awaitws_channel.disconnect(client_id)Please see the examples folder for more.
To add new channels:
- Create a subclass of
BaseChannel - Implement required methods:
connect()send()broadcast()disconnect()
- Submit a PR!
MIT
- Write tests
