Skip to content

Commit e703b6d

Browse files
Merge pull request #26 from fastapi-startkit/docs/broadcasting
docs(broadcasting): add Reverb broadcasting documentation
2 parents 346a9fb + bee689a commit e703b6d

1 file changed

Lines changed: 241 additions & 0 deletions

File tree

docs/broadcasting.md

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
---
2+
outline: deep
3+
title: Broadcasting
4+
description: Real-time event broadcasting with Reverb — a self-hosted WebSocket server that implements the Pusher protocol, built into FastAPI Startkit.
5+
keywords: broadcasting, websockets, reverb, pusher, real-time, fastapi, events, channels
6+
---
7+
8+
# Broadcasting
9+
10+
FastAPI Startkit includes a built-in broadcasting module — **Reverb** — a self-hosted WebSocket server that implements the [Pusher WebSocket protocol](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/). This means the official `pusher-js` browser SDK connects to it out of the box with **no external service required**.
11+
12+
## How It Works
13+
14+
```
15+
[FastAPI App]
16+
broadcast(OrderShipped(order))
17+
18+
19+
[ReverbManager] ──publishes to──▶ [Reverb WS Server] ◀──pusher-js──▶ [Browser]
20+
(Starlette WS)
21+
```
22+
23+
The Reverb WebSocket server runs **inside your existing FastAPI process** — no separate service to manage.
24+
25+
---
26+
27+
## Installation
28+
29+
Broadcasting is included in the core package. Register `ReverbProvider` in your application:
30+
31+
```python
32+
# bootstrap/application.py
33+
from fastapi_startkit import Application
34+
from fastapi_startkit.broadcasting import ReverbProvider
35+
36+
app = Application(
37+
base_path=...,
38+
providers=[
39+
# ... other providers
40+
ReverbProvider,
41+
]
42+
)
43+
```
44+
45+
---
46+
47+
## Configuration
48+
49+
Add the following to your `.env` file:
50+
51+
```ini
52+
BROADCAST_DRIVER=reverb
53+
54+
REVERB_APP_ID=local
55+
REVERB_APP_KEY=local
56+
REVERB_APP_SECRET=secret
57+
REVERB_HOST=0.0.0.0
58+
REVERB_PORT=8080
59+
REVERB_SCHEME=http
60+
```
61+
62+
All environment variables and their defaults:
63+
64+
| Variable | Default | Description |
65+
|---|---|---|
66+
| `BROADCAST_DRIVER` | `log` | Active driver: `reverb` or `log` |
67+
| `REVERB_APP_ID` | `1` | Reverb application ID |
68+
| `REVERB_APP_KEY` | `local` | Pusher-compatible app key sent by the client |
69+
| `REVERB_APP_SECRET` | `secret` | Secret used to sign auth requests |
70+
| `REVERB_HOST` | `0.0.0.0` | Interface the WS server binds to |
71+
| `REVERB_PORT` | `8080` | Port the WS server listens on |
72+
| `REVERB_SCHEME` | `http` | URL scheme (`http` or `https`) |
73+
74+
> [!NOTE]
75+
> The default driver is `log`. Set `BROADCAST_DRIVER=reverb` to enable real WebSocket broadcasting.
76+
77+
---
78+
79+
## Defining Events
80+
81+
Create an event class that extends `BroadcastEvent` and implements `broadcast_on()`:
82+
83+
```python
84+
from fastapi_startkit.broadcasting import BroadcastEvent, Channel
85+
86+
class OrderShipped(BroadcastEvent):
87+
def __init__(self, order_id: int, customer_email: str):
88+
self.order_id = order_id
89+
self.customer_email = customer_email
90+
91+
def broadcast_on(self):
92+
return [Channel(f"orders.{self.order_id}")]
93+
```
94+
95+
### Customising the event name
96+
97+
By default the event name is the class name (`OrderShipped`). Override `broadcast_as()` to change it:
98+
99+
```python
100+
def broadcast_as(self) -> str:
101+
return "order.shipped"
102+
```
103+
104+
### Customising the payload
105+
106+
By default `broadcast_with()` returns all instance attributes as a dict. Override it to control exactly what is sent to the client:
107+
108+
```python
109+
def broadcast_with(self) -> dict:
110+
return {
111+
"order_id": self.order_id,
112+
"status": "shipped",
113+
}
114+
```
115+
116+
### Full example
117+
118+
```python
119+
from fastapi_startkit.broadcasting import BroadcastEvent, PrivateChannel
120+
121+
class OrderShipped(BroadcastEvent):
122+
def __init__(self, order_id: int, customer_email: str):
123+
self.order_id = order_id
124+
self.customer_email = customer_email
125+
126+
def broadcast_on(self):
127+
return [PrivateChannel(f"orders.{self.order_id}")]
128+
129+
def broadcast_as(self) -> str:
130+
return "OrderShipped"
131+
132+
def broadcast_with(self) -> dict:
133+
return {
134+
"order_id": self.order_id,
135+
"status": "shipped",
136+
}
137+
```
138+
139+
---
140+
141+
## Channel Types
142+
143+
| Class | Channel prefix | Use case |
144+
|---|---|---|
145+
| `Channel` | _(none)_ | Public — anyone can subscribe |
146+
| `PrivateChannel` | `private-` | Authenticated users only |
147+
| `PresenceChannel` | `presence-` | Authenticated + user list |
148+
149+
```python
150+
from fastapi_startkit.broadcasting import Channel, PrivateChannel, PresenceChannel
151+
152+
# Public channel — broadcast to "orders.1"
153+
def broadcast_on(self):
154+
return [Channel(f"orders.{self.order_id}")]
155+
156+
# Private channel — broadcast to "private-orders.1"
157+
def broadcast_on(self):
158+
return [PrivateChannel(f"orders.{self.order_id}")]
159+
160+
# Presence channel — broadcast to "presence-orders.1"
161+
def broadcast_on(self):
162+
return [PresenceChannel(f"orders.{self.order_id}")]
163+
```
164+
165+
---
166+
167+
## Broadcasting Events
168+
169+
### Using the helper function
170+
171+
```python
172+
from fastapi_startkit.broadcasting import broadcast
173+
174+
@router.post("/orders/{order_id}/ship")
175+
async def ship_order(order_id: int):
176+
# ... business logic ...
177+
await broadcast(OrderShipped(order_id, customer_email="user@example.com"))
178+
return {"status": "shipped"}
179+
```
180+
181+
### Using the Facade
182+
183+
```python
184+
from fastapi_startkit.facades.Broadcast import Broadcast
185+
186+
@router.post("/orders/{order_id}/ship")
187+
async def ship_order(order_id: int):
188+
await Broadcast.event(OrderShipped(order_id, customer_email="user@example.com"))
189+
return {"status": "shipped"}
190+
```
191+
192+
---
193+
194+
## Local Development
195+
196+
During development, set `BROADCAST_DRIVER=log` to print events to the console without needing a WebSocket connection:
197+
198+
```ini
199+
BROADCAST_DRIVER=log
200+
```
201+
202+
When the log driver is active, each `broadcast()` call writes a structured line to the `reverb` logger:
203+
204+
```
205+
[Broadcast] channel=orders.1 event=OrderShipped data={'order_id': 1, 'status': 'shipped'}
206+
```
207+
208+
`log` is the **default driver**, so if `BROADCAST_DRIVER` is not set at all, events are safely logged and never sent over WebSockets. This makes it safe to leave broadcasting calls in your code during testing without any WebSocket infrastructure.
209+
210+
---
211+
212+
## Frontend (pusher-js)
213+
214+
Install `pusher-js` in your frontend project:
215+
216+
```bash
217+
npm install pusher-js
218+
```
219+
220+
Connect to the Reverb server — point `wsHost` and `wsPort` at your FastAPI app:
221+
222+
```js
223+
import Pusher from "pusher-js";
224+
225+
const pusher = new Pusher("local", {
226+
wsHost: "127.0.0.1",
227+
wsPort: 8080,
228+
forceTLS: false,
229+
enabledTransports: ["ws"],
230+
cluster: "mt1",
231+
});
232+
233+
const channel = pusher.subscribe("orders.1");
234+
235+
channel.bind("OrderShipped", (data) => {
236+
console.log("Order shipped:", data);
237+
});
238+
```
239+
240+
> [!TIP]
241+
> The first argument to `new Pusher(...)` is the `REVERB_APP_KEY`. It must match the value configured in your `.env` file.

0 commit comments

Comments
 (0)