Skip to content

Commit 0affb7c

Browse files
authored
realtime: Broadcast Changes documentation (supabase#31222)
1 parent bbf806d commit 0affb7c

File tree

5 files changed

+236
-2
lines changed

5 files changed

+236
-2
lines changed

apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,10 @@ export const realtime: NavMenuConstant = {
15591559
name: 'Listening to Postgres Changes with Flutter',
15601560
url: '/guides/realtime/realtime-listening-flutter',
15611561
},
1562+
{
1563+
name: 'Migrate to Broadcast Changes',
1564+
url: '/guides/realtime/migrate-from-postgres-changes',
1565+
},
15621566
],
15631567
},
15641568
{

apps/docs/content/guides/realtime/broadcast.mdx

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ subtitle: 'Send and receive messages using Realtime Broadcast'
44
description: 'Send and receive messages using Realtime Broadcast'
55
---
66

7-
Let's explore how to implement Realtime Broadcast to send messages between clients.
7+
Let's explore how to implement Realtime Broadcast to send messages between clients using either WebSockets, REST API or triggers from your database.
88

99
## Usage
1010

@@ -88,7 +88,7 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj
8888

8989
### Listening to broadcast messages
9090

91-
You can provide a callback for the `broadcast` channel to receive message. In this example we will receive any `broadcast` messages in `room-1`:
91+
You can provide a callback for the `broadcast` channel to receive message. This example will receive any `broadcast` messages in `room-1`:
9292

9393
<Tabs
9494
scrollable
@@ -637,3 +637,115 @@ You can also send a Broadcast message by making an HTTP request to Realtime serv
637637
Unsupported in Python yet.
638638
</TabPanel>
639639
</Tabs>
640+
641+
## Trigger broadcast messages from your database
642+
643+
<Admonition type="caution">
644+
645+
This feature is currently in Private Alpha. The API and implementation may change. To request access, submit a Support Ticket.
646+
647+
</Admonition>
648+
649+
### How it works
650+
651+
Broadcast Changes allows you to trigger messages from your database. To achieve it Realtime is directly reading your WAL (Write Append Log) file using a publication against the `realtime.messages` table so whenever a new insert happens a message is sent to connected users.
652+
653+
It uses partitioned tables per day which allows the deletion your previous images in a performant way by dropping the physical tables of this partitioned table. Tables older than 3 days old are deleted.
654+
655+
Broadcasting from the database works like a client-side broadcast, using WebSockets to send JSON packages. [Realtime Authorization]/docs/guides/realtime/authorization) is required and enabled by default to protect your data.
656+
657+
The database broadcast feature provides two functions to help you send messages:
658+
659+
- `realtime.send` will insert a message into realtime.messages without a specific format.
660+
- `realtime.broadcast_changes` will insert a message with the required fields to emit database changes to clients. This helps you set up triggers on your tables to emit changes.
661+
662+
### Broadcasting a message from your database
663+
664+
The `realtime.send` function provides the most flexibility by allowing you to broadcast messages from your database without a specific format. This allows you to use database broadcast for messages that aren't necessarily tied to the shape of a Postgres row change.
665+
666+
```sql
667+
SELECT realtime.send (
668+
to_jsonb ('{}'::text), -- JSONB Payload
669+
'event', -- Event name
670+
'topic', -- Topic
671+
FALSE -- Public / Private flag
672+
);
673+
```
674+
675+
### Broadcast record changes
676+
677+
#### Setup realtime authorization
678+
679+
Realtime Authorization is required and enabled by default. To allow your users to listen to messages from topics, create a RLS (Row Level Security) policy:
680+
681+
```sql
682+
CREATE POLICY "authenticated can receive broadcasts"
683+
ON "realtime"."messages"
684+
FOR SELECT
685+
TO authenticated
686+
USING ( true );
687+
688+
```
689+
690+
See the [Realtime Authorization](/docs/guides/realtime/authorization) docs to learn how to set up more specific policies.
691+
692+
#### Set up trigger function
693+
694+
First, set up a trigger function that uses `realtime.broadcast_changes` to insert an event whenever it is triggered. The event is set up to include data on the schema, table, operation, and field changes that triggered it.
695+
696+
For this example use case, we want to have a topic with the name `topic:<record id>` to which we're going to broadcast events.
697+
698+
```sql
699+
CREATE OR REPLACE FUNCTION public.your_table_changes() RETURNS trigger AS $$
700+
BEGIN
701+
PERFORM realtime.broadcast_changes(
702+
'topic:' || NEW.id::text, -- topic
703+
TG_OP, -- event
704+
TG_OP, -- operation
705+
TG_TABLE_NAME, -- table
706+
TG_TABLE_SCHEMA, -- schema
707+
NEW, -- new record
708+
OLD -- old record
709+
);
710+
RETURN NULL;
711+
END;
712+
$$ LANGUAGE plpgsql;
713+
```
714+
715+
Of note are the Postgres native trigger special variables used:
716+
717+
- `TG_OP` - the operation that triggered the function
718+
- `TG_TABLE_NAME` - the table that caused the trigger
719+
- `TG_TABLE_SCHEMA` - the schema of the table that caused the trigger invocation
720+
- `NEW` - the record after the change
721+
- `OLD` - the record before the change
722+
723+
You can read more about them in this [guide](https://www.postgresql.org/docs/current/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER).
724+
725+
#### Set up trigger
726+
727+
Next, set up a trigger so the function runs whenever your target table has a change.
728+
729+
```sql
730+
CREATE TRIGGER broadcast_changes_for_your_table_trigger
731+
AFTER INSERT OR UPDATE OR DELETE ON public.your_table
732+
FOR EACH ROW
733+
EXECUTE FUNCTION your_table_changes ();
734+
```
735+
736+
As you can see, it will be broadcasting all operations so our users will receive events when records are inserted, updated or deleted from `public.your_table` .
737+
738+
#### Listen on client side
739+
740+
Finally, client side will requires to be set up to listen to the topic `topic:<record id>` to receive the events.
741+
742+
```jsx
743+
const gameId = 'id'
744+
await supabase.realtime.setAuth() // Needed for Realtime Authorization
745+
const changes = supabase
746+
.channel(`topic:${gameId}`)
747+
.on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload))
748+
.on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload))
749+
.on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload))
750+
.subscribe()
751+
```
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
---
2+
title: 'Migrate to Broadcast Changes'
3+
subtitle: 'How to migrate from Postgres Changes to Broadcast Changes'
4+
description: 'How to migrate from Postgres Changes to Broadcast Changes'
5+
sidebar_label: 'Migrate to Broadcast Changes'
6+
---
7+
8+
Postgres Changes has some [limitations](/docs/guides/realtime/postgres-changes#limitations) as your application scales. To continue broadcasting database changes to users as you scale, you can use Broadcast Changes.
9+
10+
## Example application using Postgres Changes
11+
12+
Here we have a simple chess application that has a game id and we want to track whenever we have new moves happening for a given game id.
13+
14+
We store this information in a `public.moves` table and every time a new move is added to a given `game_id` we want to receive the changes in our connected Realtime client
15+
16+
<Image
17+
alt="Schema used for our example"
18+
src={{
19+
light:
20+
'docs/public/img/guides/realtime/realtime-broadcast-changes-migration-schema-example-light.png',
21+
dark: 'docs/public/img/guides/realtime/realtime-broadcast-changes-migration-schema-example-dark.png',
22+
}}
23+
/>
24+
25+
In our client we will have our implementation to receive insert events with the usual code:
26+
27+
```javascript
28+
const gameId = '4a8bbe89-f601-4414-bd47-8d0f7ab2a31a'
29+
const changes = supabase
30+
.channel('chess-moves')
31+
.on(
32+
'postgres_changes',
33+
{
34+
event: 'INSERT',
35+
schema: 'public',
36+
table: 'moves',
37+
filter: `game_id=eq.${gameId}`,
38+
},
39+
(payload) => console.log(payload)
40+
)
41+
.subscribe()
42+
...
43+
```
44+
45+
## Migrate to broadcast changes
46+
47+
To use Broadcast Changes, first familiarize yourself with the [Broadcast Changes implementation](/docs/guides/realtime/broadcast#trigger-broadcast-messages-from-your-database).
48+
49+
### Set up authorization
50+
51+
Broadcast Changes is private by default, using [Realtime Authorization](/docs/guides/realtime/authorization) to control access. First, set up RLS policies to control user access to relevant messages:
52+
53+
```sql
54+
CREATE POLICY "authenticated can listen to game moves"
55+
ON "realtime"."messages"
56+
FOR SELECT
57+
TO authenticated
58+
USING (
59+
EXISTS (
60+
SELECT 1
61+
FROM game_users
62+
WHERE (SELECT auth.uid()) = user_id
63+
AND (select realtime.topic()) = 'games:' || game_id::text
64+
AND realtime.messages.extension = 'broadcast'
65+
)
66+
);
67+
```
68+
69+
### Set up trigger function
70+
71+
We need to define our trigger function to adapt to our use case and use the provided function `realtime.broadcast_changes`
72+
73+
```sql
74+
CREATE OR REPLACE FUNCTION public.broadcast_moves() RETURNS trigger AS $$
75+
BEGIN
76+
PERFORM realtime.broadcast_changes(
77+
'games:' || NEW.game_id::text, -- topic
78+
TG_OP, -- event
79+
TG_OP, -- operation
80+
TG_TABLE_NAME, -- table
81+
TG_TABLE_SCHEMA, -- schema
82+
NEW, -- new record
83+
OLD -- old record
84+
);
85+
RETURN NULL;
86+
END;
87+
$$ LANGUAGE plpgsql;
88+
```
89+
90+
### Setup trigger with created function
91+
92+
Now we need to setup our trigger to capture the events we want
93+
94+
```sql
95+
CREATE TRIGGER chess_move_changes
96+
AFTER INSERT ON public.moves
97+
FOR EACH ROW
98+
EXECUTE FUNCTION public.broadcast_moves();
99+
```
100+
101+
### **Listen to changes in client**
102+
103+
Finally you can setup your client to listen for your events
104+
105+
```js
106+
const gameId = '4a8bbe89-f601-4414-bd47-8d0f7ab2a31a'
107+
await supabase.realtime.setAuth() // Needed for Realtime Authorization
108+
const changes = supabase
109+
.channel(`games:${gameId}`)
110+
.on(
111+
'broadcast',
112+
{
113+
event: 'INSERT',
114+
},
115+
(payload) => console.log(payload)
116+
)
117+
.subscribe()
118+
```
36.4 KB
Loading
39.9 KB
Loading

0 commit comments

Comments
 (0)