Realtime Architecture and Concepts
What is Realtime?
In traditional web applications, clients request data and servers respond. To get updates, clients must request again. Realtime changes this paradigm—servers push updates to clients as they happen.
The Traditional Request-Response Model
┌─────────┐ ┌─────────┐
│ Client │ 1. Request: GET /posts │ Server │
│ │ ─────────────────────────────│ │
│ │ │ │
│ │ 2. Response: [posts...] │ │
│ │ ←─────────────────────────── │ │
│ │ │ │
│ Time │ 3. Wait... │ │
│ passes │ │ │
│ │ 4. Request: GET /posts │ │
│ │ ─────────────────────────────│ │
│ │ │ │
│ │ 5. Response: [posts...] │ │
│ │ ←─────────────────────────── │ │
└─────────┘ └─────────┘
Problem: Client doesn't know when new data exists
Solution: Poll repeatedly (inefficient)
The Realtime Model
┌─────────┐ ┌─────────┐
│ Client │ 1. Subscribe to changes │ Server │
│ │ ═════════════════════════════│ │
│ │ (WebSocket connection) │ │
│ │ │ │
│ │ │ ← Data │
│ │ 2. Push: New post created │ changes│
│ │ ←════════════════════════════│ │
│ │ │ │
│ │ │ ← Data │
│ │ 3. Push: Post updated │ changes│
│ │ ←════════════════════════════│ │
│ │ │ │
│ │ 4. Unsubscribe │ │
│ │ ═════════════════════════════│ │
└─────────┘ └─────────┘
Benefit: Instant updates, no polling
Supabase Realtime Architecture
Supabase Realtime is built on several technologies:
┌─────────────────────────────────────────────────────────────┐
│ Client Applications │
│ (Web, Mobile, Desktop) │
└─────────────────────────────────────────────────────────────┘
│
WebSocket Connection
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Supabase Realtime Server │
│ (Phoenix/Elixir) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Channels │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │ Postgres │ │Broadcast │ │ Presence │ │ │
│ │ │ Changes │ │ │ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ PostgreSQL Replication
▼
┌─────────────────────────────────────────────────────────────┐
│ PostgreSQL │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Write-Ahead Log (WAL) │ │
│ │ (Records all database changes) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Three Types of Realtime Features
1. Postgres Changes
Listen to database INSERT, UPDATE, DELETE events:
// Subscribe to new posts
supabase
.channel('posts')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'posts'
}, (payload) => {
console.log('New post:', payload.new)
})
.subscribe()
2. Broadcast
Send arbitrary messages to connected clients:
// Send a message to all clients in the room
const channel = supabase.channel('room:123')
channel.send({
type: 'broadcast',
event: 'cursor_move',
payload: { x: 100, y: 200, userId: 'abc' }
})
3. Presence
Track who's online and their state:
// Track user presence
const channel = supabase.channel('room:123')
channel.on('presence', { event: 'sync' }, () => {
const state = channel.presenceState()
console.log('Online users:', Object.keys(state))
})
channel.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await channel.track({
user_id: currentUser.id,
online_at: new Date().toISOString()
})
}
})
How Postgres Changes Works
Supabase uses PostgreSQL's logical replication feature to detect changes:
Write-Ahead Log (WAL)
PostgreSQL records all changes to a log before applying them:
WAL Entry Example:
┌──────────────────────────────────────────────────────────┐
│ Transaction: 12345 │
│ Table: public.posts │
│ Operation: INSERT │
│ New Row: {id: 'uuid', title: 'Hello', content: '...'} │
│ Timestamp: 2024-01-15 10:30:00 │
└──────────────────────────────────────────────────────────┘
Replication Slots
Supabase creates a replication slot that reads the WAL:
┌────────────────┐ ┌────────────────┐ ┌─────────────────┐
│ PostgreSQL │────→│ Replication │────→│ Realtime │
│ (WAL) │ │ Slot │ │ Server │
└────────────────┘ └────────────────┘ └─────────────────┘
Change Filtering
The Realtime server filters changes based on subscriptions:
// Client subscribes to specific table and filter
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: 'room_id=eq.123' // Only messages in room 123
}, handleNewMessage)
The server only sends changes matching the subscription.
WebSocket Connections
What is a WebSocket?
A WebSocket is a persistent, bidirectional connection:
HTTP (Traditional):
Client ─── Request ──→ Server
Client ←── Response ─── Server
(Connection closes)
WebSocket (Realtime):
Client ←═══════════════→ Server
(Connection stays open)
(Either side can send anytime)
Connection Lifecycle
1. Client initiates WebSocket handshake
GET /realtime/v1/websocket HTTP/1.1
Upgrade: websocket
2. Server accepts
HTTP/1.1 101 Switching Protocols
3. Connection established
Client ←════════════════→ Server
4. Messages flow both directions
5. Either side can close
Connection Management
The Supabase SDK handles connection management:
const channel = supabase.channel('my-channel')
// Subscribe (opens connection if needed)
channel.subscribe((status) => {
console.log('Status:', status)
// SUBSCRIBED, CHANNEL_ERROR, TIMED_OUT, CLOSED
})
// Unsubscribe (closes when no more subscriptions)
channel.unsubscribe()
Channels
A channel is a named communication pipe:
// Different channels for different purposes
const postsChannel = supabase.channel('posts')
const chatChannel = supabase.channel('chat:room-123')
const presenceChannel = supabase.channel('presence:lobby')
Channel Naming Conventions
posts // Simple name
chat:room-123 // Hierarchical (chat feature, specific room)
user:uuid:updates // User-specific channel
Multiple Subscriptions Per Channel
One channel can have multiple event handlers:
const channel = supabase.channel('app-events')
channel
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'posts'
}, handlePostChange)
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'comments'
}, handleCommentChange)
.on('broadcast', {
event: 'notification'
}, handleNotification)
.subscribe()
RLS and Realtime
Important: Row Level Security applies to Realtime subscriptions!
-- RLS policy
CREATE POLICY "Users see own posts"
ON posts FOR SELECT
USING (user_id = auth.uid());
-- Realtime subscription
// User A subscribes to posts
supabase.channel('posts').on('postgres_changes', {
event: '*',
schema: 'public',
table: 'posts'
}, handler).subscribe()
// When User B creates a post:
// - User A only receives the event if the RLS policy allows
// - User A's auth.uid() is checked against the new row
What This Means
- You can't receive events for data you can't query
- Subscriptions are automatically filtered by RLS
- Security is consistent across API and Realtime
Use Cases
Live Dashboards
// Subscribe to metrics changes
supabase
.channel('metrics')
.on('postgres_changes', {
event: '*',
schema: 'public',
table: 'metrics'
}, updateDashboard)
.subscribe()
Chat Applications
// Subscribe to messages in a room
supabase
.channel(`room:${roomId}`)
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}`
}, displayMessage)
.subscribe()
Collaborative Editing
// Broadcast cursor positions
channel.send({
type: 'broadcast',
event: 'cursor',
payload: { userId, position }
})
// Track presence
channel.track({ userId, name, status: 'editing' })
Notifications
// Subscribe to user-specific notifications
supabase
.channel(`user:${userId}`)
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'notifications',
filter: `user_id=eq.${userId}`
}, showNotification)
.subscribe()
Key Takeaways
- Realtime enables push: Server sends data as it changes
- Three features: Postgres Changes, Broadcast, Presence
- Built on PostgreSQL WAL: Reliable change detection
- WebSocket connections: Persistent, bidirectional
- Channels organize subscriptions: Named communication pipes
- RLS applies: Security consistent with queries
Looking Ahead
Now that you understand the architecture, we'll dive deeper into Postgres Changes and how Change Data Capture works.
Realtime transforms user experiences. Instead of stale data and manual refresh buttons, your applications can show the world as it changes, instantly.

