Home

Realtime Quickstart

Learn how to build multiplayer.dev, a collaborative app that demonstrates Broadcast, Presence, and Postgres Changes using Realtime.

Install supabase-js Client#


_10
npm 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:


_28
const { createClient } = require('@supabase/supabase-js')
_28
_28
const 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.
_28
const channel = supabase.channel('room1')
_28
_28
// Subscribe registers your client with the server
_28
channel.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.
_11
supabase
_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
_23
const channel = supabase.channel('calc-latency', {
_23
config: {
_23
broadcast: { ack: true },
_23
},
_23
})
_23
_23
channel.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
_28
const channel = supabase.channel('online-users', {
_28
config: {
_28
presence: {
_28
key: 'user1',
_28
},
_28
},
_28
})
_28
_28
channel.on('presence', { event: 'sync' }, () => {
_28
console.log('Online users: ', channel.presenceState())
_28
})
_28
_28
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
_28
console.log('New users have joined: ', newPresences)
_28
})
_28
_28
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
_28
console.log('Users have left: ', leftPresences)
_28
})
_28
_28
channel.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
_18
const channel = supabase.channel('online-users', {
_18
config: {
_18
presence: {
_18
key: 'user2',
_18
},
_18
},
_18
})
_18
_18
// Presence event handlers setup
_18
_18
channel.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:


_21
create 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
_21
alter table messages enable row level security;
_21
_21
create policy "anon_ins_policy"
_21
ON messages
_21
for insert
_21
to anon
_21
with check (true);
_21
_21
create policy "anon_sel_policy"
_21
ON messages
_21
for select
_21
to anon
_21
using (true);

If it doesn't already exist, create a supabase_realtime publication and add messages table to the publication:


_10
begin;
_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');
_10
commit;
_10
_10
-- add a table to the publication
_10
alter 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
_28
const channel = supabase.channel('db-messages')
_28
_28
const roomId = 'room1'
_28
const userId = 'user1'
_28
_28
channel.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
_28
channel.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
})