Skip to main content

Realtime

NaijaBase Realtime lets you subscribe to database changes and push live updates to every connected client — no polling required. It runs over a persistent WebSocket connection backed by PostgreSQL's LISTEN/NOTIFY system.

How it works

Browser → WebSocket → NaijaBase API → PostgreSQL LISTEN/NOTIFY

When a row changes (INSERT, UPDATE, or DELETE), PostgreSQL fires a NOTIFY event. NaijaBase picks it up and forwards the payload to every subscribed client over their open WebSocket.

Connect

JavaScript SDK

import { createClient } from '@naijabase/js'

const nb = createClient('https://api.naijabase.dev', 'nb_anon_...')

// Subscribe to all changes on a table
const channel = nb.channel('public:messages')

channel
.on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, (payload) => {
console.log('Change received:', payload)
})
.subscribe()

Filter by event type

// Only listen for new rows
channel.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, handler)

// Only listen for updates
channel.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'messages' }, handler)

// Only listen for deletes
channel.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'messages' }, handler)

Filter by column value

// Only receive changes for a specific room
channel.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'messages', filter: 'room_id=eq.123' },
handler
)

Unsubscribe

// Remove one channel
await nb.removeChannel(channel)

// Remove all channels
await nb.removeAllChannels()

Presence (who is online)

Track which users are online in a channel:

const presenceChannel = nb.channel('room-123')

presenceChannel
.on('presence', { event: 'sync' }, () => {
const state = presenceChannel.presenceState()
console.log('Online users:', state)
})
.on('presence', { event: 'join' }, ({ newPresences }) => {
console.log('User joined:', newPresences)
})
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
console.log('User left:', leftPresences)
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await presenceChannel.track({ user_id: 'abc', online_at: new Date().toISOString() })
}
})

Broadcast

Send ephemeral messages to channel subscribers without touching the database:

const broadcastChannel = nb.channel('typing-indicators')

// Listen for typing events
broadcastChannel
.on('broadcast', { event: 'typing' }, ({ payload }) => {
console.log(`${payload.username} is typing...`)
})
.subscribe()

// Emit a typing event
await broadcastChannel.send({
type: 'broadcast',
event: 'typing',
payload: { username: 'Ada' },
})

Live chat example

import { useEffect, useState } from 'react'
import { createClient } from '@naijabase/js'

const nb = createClient(process.env.NEXT_PUBLIC_NAIJABASE_URL, process.env.NEXT_PUBLIC_NAIJABASE_ANON_KEY)

export default function Chat({ roomId }) {
const [messages, setMessages] = useState([])

useEffect(() => {
// Load existing messages
nb.from('messages')
.select('*')
.eq('room_id', roomId)
.order('created_at')
.then(({ data }) => setMessages(data ?? []))

// Subscribe to new messages
const channel = nb
.channel(`room-${roomId}`)
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages', filter: `room_id=eq.${roomId}` },
({ new: msg }) => setMessages((prev) => [...prev, msg])
)
.subscribe()

return () => { nb.removeChannel(channel) }
}, [roomId])

async function sendMessage(text) {
await nb.from('messages').insert({ room_id: roomId, text, user_id: (await nb.auth.getUser()).data.user.id })
}

return (
<div>
{messages.map((m) => <p key={m.id}>{m.text}</p>)}
</div>
)
}

RLS and Realtime

Realtime respects Row Level Security. A client only receives change events for rows their current role can SELECT. To receive events for a table, the connected user's role must have a SELECT policy on that table.

-- Users only receive changes for their own messages
CREATE POLICY "receive own messages"
ON messages
FOR SELECT
USING (user_id = auth.uid());

Direct PostgreSQL LISTEN/NOTIFY

You can also use NOTIFY from your server-side code or triggers:

-- Trigger that notifies on insert
CREATE OR REPLACE FUNCTION notify_message_insert()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('messages', row_to_json(NEW)::text);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER on_message_insert
AFTER INSERT ON messages
FOR EACH ROW EXECUTE FUNCTION notify_message_insert();

Limits

FeatureValue
Max concurrent connections500 per project
Max channels per client100
Payload size64 KB
Presence state per key8 KB