Back to Blog
Development

Building Real-time Features with Supabase

Michael Chen
10 min read

Real-time functionality has become essential for modern web applications. From collaborative editing to live notifications, users expect instant updates. Supabase makes it remarkably simple to add real-time features to your applications.

Understanding Supabase Realtime

Supabase Realtime is built on PostgreSQL's replication functionality and uses WebSockets to push changes to connected clients. It supports three main features:

  • Database changes (inserts, updates, deletes)
  • Presence (tracking online users)
  • Broadcast (sending messages between clients)

Setting Up Realtime

First, enable Realtime for your table:

ALTER PUBLICATION supabase_realtime ADD TABLE messages;

Then subscribe to changes in your application:

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

Building a Chat Application

Let's build a real-time chat:

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

// Send a message const sendMessage = async (content: string) => { const { error } = await supabase .from('messages') .insert({ content, room_id: roomId, user_id: userId }) if (error) console.error('Error sending message:', error) } ```

Implementing Presence

Track who's online in real-time:

const channel = supabase.channel('online-users')

// Track presence channel .on('presence', { event: 'sync' }, () => { const state = channel.presenceState() const users = Object.values(state).flat() setOnlineUsers(users) }) .subscribe(async (status) => { if (status === 'SUBSCRIBED') { await channel.track({ user_id: userId, username: username, online_at: new Date().toISOString() }) } }) ```

Broadcast for Ephemeral Data

Send temporary data without touching the database:

// Typing indicators
const sendTypingStatus = () => {
  channel.send({
    type: 'broadcast',
    event: 'typing',
    payload: { user_id: userId, username: username }
  })
}

// Listen for typing channel.on('broadcast', { event: 'typing' }, (payload) => { setTypingUsers(prev => [...prev, payload.payload.username]) }) ```

Performance Optimization

Filtering at the Database Level

Always filter subscriptions to reduce bandwidth:

.on('postgres_changes',
  {
    event: '*',
    schema: 'public',
    table: 'posts',
    filter: `organization_id=eq.${orgId}`
  },
  handleChange
)

Throttling Updates

Prevent overwhelming clients with too many updates:

import { throttle } from 'lodash'

const throttledUpdate = throttle((payload) => { updateUI(payload) }, 100) ```

Error Handling

Always handle connection errors gracefully:

channel
  .subscribe((status, error) => {
    if (status === 'CHANNEL_ERROR') {
      console.error('Subscription error:', error)
      // Attempt reconnection
      setTimeout(() => channel.subscribe(), 5000)
    }
  })

Best Practices

  • Use filters to minimize unnecessary data transfer
  • Implement reconnection logic for network issues
  • Clean up subscriptions when components unmount
  • Use broadcast for ephemeral data
  • Test with poor network conditions

Conclusion

Supabase Realtime makes it straightforward to add real-time features to your applications. By understanding the three core features—database changes, presence, and broadcast—you can build engaging, collaborative experiences that users love.