Realtime Quickstart
Learn how to build multiplayer.dev, a collaborative app that demonstrates Broadcast, Presence, and Postgres Changes using Realtime.
Install supabase-js
Client#
_10npm install @supabase/supabase-js
Cursor Positions#
Broadcast allows a client to send messages and multiple clients to receive the messages. The broadcasted messages are ephemeral. They are not persisted to the database and are directly relayed through the Realtime servers. This is ideal for sending information like cursor positions where minimal latency is important, but persisting them is not.
In multiplayer.dev, client's cursor positions are sent to other clients in the room. However, cursor positions will be randomly generated for this example.
You need to get the public anon
access token from your project's API settings. Then you can set up the Supabase client and start sending a client's cursor positions to other clients in channel room1
:
_28const { createClient } = require('@supabase/supabase-js')_28_28const supabase = createClient('https://your-project-ref.supabase.co', 'anon-key', {_28 realtime: {_28 params: {_28 eventsPerSecond: 10,_28 },_28 },_28})_28_28// Channel name can be any string._28// Create channels with the same name for both the broadcasting and receiving clients._28const channel = supabase.channel('room1')_28_28// Subscribe registers your client with the server_28channel.subscribe((status) => {_28 if (status === 'SUBSCRIBED') {_28 // now you can start broadcasting cursor positions_28 setInterval(() => {_28 channel.send({_28 type: 'broadcast',_28 event: 'cursor-pos',_28 payload: { x: Math.random(), y: Math.random() },_28 })_28 console.log(status)_28 }, 100)_28 }_28})
info
JavaScript client has a default rate limit of 1 Realtime event every 100 milliseconds that's configured by eventsPerSecond
.
Another client can subscribe to channel room1
and receive cursor positions:
_11// Supabase client setup_11_11// Listen to broadcast messages._11supabase_11 .channel('room1')_11 .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))_11 .subscribe((status) => {_11 if (status === 'SUBSCRIBED') {_11 // your callback function will now be called with the messages broadcast by the other client_11 }_11 })
info
type
must be broadcast
and the event
must match for clients subscribed to the channel.
Roundtrip Latency#
You can also configure the channel so that the server must return an acknowledgement that it received the broadcast
message. This is useful if you want to measure the roundtrip latency:
_23// Supabase client setup_23_23const channel = supabase.channel('calc-latency', {_23 config: {_23 broadcast: { ack: true },_23 },_23})_23_23channel.subscribe(async (status) => {_23 if (status === 'SUBSCRIBED') {_23 const begin = performance.now()_23_23 await channel.send({_23 type: 'broadcast',_23 event: 'latency',_23 payload: {},_23 })_23_23 const end = performance.now()_23_23 console.log(`Latency is ${end - begin} milliseconds`)_23 }_23})
Track and Display Which Users Are Online#
Presence stores and synchronize shared state across clients. The sync
event is triggered whenever the shared state changes. The join
event is triggered when new clients join the channel and leave
event is triggered when clients leave.
Each client can use the channel's track
method to store an object in shared state. Each client can only track one object, and if track
is called again by the same client, then the new object overwrites the previously tracked object in the shared state. You can use one client to track and display users who are online:
_28// Supabase client setup_28_28const channel = supabase.channel('online-users', {_28 config: {_28 presence: {_28 key: 'user1',_28 },_28 },_28})_28_28channel.on('presence', { event: 'sync' }, () => {_28 console.log('Online users: ', channel.presenceState())_28})_28_28channel.on('presence', { event: 'join' }, ({ newPresences }) => {_28 console.log('New users have joined: ', newPresences)_28})_28_28channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {_28 console.log('Users have left: ', leftPresences)_28})_28_28channel.subscribe(async (status) => {_28 if (status === 'SUBSCRIBED') {_28 const status = await channel.track({ online_at: new Date().toISOString() })_28 console.log(status)_28 }_28})
Then you can use another client to add another user to the channel's Presence state:
_18// Supabase client setup_18_18const channel = supabase.channel('online-users', {_18 config: {_18 presence: {_18 key: 'user2',_18 },_18 },_18})_18_18// Presence event handlers setup_18_18channel.subscribe(async (status) => {_18 if (status === 'SUBSCRIBED') {_18 const status = await channel.track({ online_at: new Date().toISOString() })_18 console.log(status)_18 }_18})
If a channel is set up without a presence key, the server generates a random UUID. type
must be presence
and event
must be either sync
, join
, or leave
.
Insert and Receive Persisted Messages#
Postgres Changes enables your client to insert, update, or delete database records and send the changes to clients. Create a messages
table to keep track of messages created by users in specific rooms:
_21create table messages (_21 id serial primary key,_21 message text,_21 user_id text,_21 room_id text,_21 created_at timestamptz default now()_21)_21_21alter table messages enable row level security;_21_21create policy "anon_ins_policy"_21ON messages_21for insert_21to anon_21with check (true);_21_21create policy "anon_sel_policy"_21ON messages_21for select_21to anon_21using (true);
If it doesn't already exist, create a supabase_realtime
publication and add messages
table to the publication:
_10begin;_10 -- remove the supabase_realtime publication_10 drop publication if exists supabase_realtime;_10_10 -- re-create the supabase_realtime publication with no tables and only for insert_10 create publication supabase_realtime with (publish = 'insert');_10commit;_10_10-- add a table to the publication_10alter publication supabase_realtime add table messages;
You can then have a client listen for changes on the messages
table for a specific room and send and receive persisted messages:
_28// Supabase client setup_28_28const channel = supabase.channel('db-messages')_28_28const roomId = 'room1'_28const userId = 'user1'_28_28channel.on(_28 'postgres_changes',_28 {_28 event: 'INSERT',_28 schema: 'public',_28 table: 'messages',_28 filter: `room_id=eq.${roomId}`,_28 },_28 (payload) => console.log(payload)_28)_28_28channel.subscribe(async (status) => {_28 if (status === 'SUBSCRIBED') {_28 const res = await supabase.from('messages').insert({_28 room_id: roomId,_28 user_id: userId,_28 message: 'Welcome to Realtime!',_28 })_28 console.log(res)_28 }_28})