Back to Supabase

Getting Started with Realtime

apps/docs/content/guides/realtime/getting_started.mdx

1.26.0415.6 KB
Original Source

Quick start

1. Install the client library

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
bash
npm install @supabase/supabase-js
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
bash
flutter pub add supabase_flutter
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
let package = Package(
    // ...
    dependencies: [
        // ...
        .package(
            url: "https://github.com/supabase/supabase-swift.git",
            from: "2.0.0"
        ),
    ],
    targets: [
        .target(
            name: "YourTargetName",
            dependencies: [
                .product(
                    name: "Supabase",
                    package: "supabase-swift"
                ),
            ]
        )
    ]
)
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python - PIP">
bash
pip install supabase
</TabPanel> <TabPanel id="python" label="Python - Conda">
bash
conda install -c conda-forge supabase
</TabPanel> </$Show> </Tabs>

2. Initialize the client

Get your project URL and key. <$Partial path="api_settings.mdx" variables={{ "framework": "", "tab": "" }} />

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
ts
import { createClient } from '@supabase/supabase-js'

const supabase = createClient('https://<project>.supabase.co', '<anon_key or sb_publishable_key>')
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
dart
import 'package:supabase_flutter/supabase_flutter.dart';

void main() async {
  await Supabase.initialize(
    url: 'https://<project>.supabase.co',
    anonKey: '<anon_key or sb_publishable_key>',
  );
  runApp(MyApp());
}

final supabase = Supabase.instance.client;
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
import Supabase

let supabase = SupabaseClient(
  supabaseURL: URL(string: "https://<project>.supabase.co")!,
  supabaseKey: "<anon_key or sb_publishable_key>"
)
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python">
python
from supabase import create_client, Client

url: str = "https://<project>.supabase.co"
key: str = "<anon_key or sb_publishable_key>"
supabase: Client = create_client(url, key)
</TabPanel> </$Show> </Tabs>

3. Create your first Channel

Channels are the foundation of Realtime. Think of them as rooms where clients can communicate. Each channel is identified by a topic name and if they are public or private.

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
ts
// Create a channel with a descriptive topic name
const channel = supabase.channel('room:lobby:messages', {
  config: { private: true }, // Recommended for production
})
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
dart
// Create a channel with a descriptive topic name
final channel = supabase.channel('room:lobby:messages');
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
// Create a channel with a descriptive topic name
let channel = supabase.channel("room:lobby:messages") {
  $0.isPrivate = true
}
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python">
python
# Create a channel with a descriptive topic name
channel = supabase.channel('room:lobby:messages', params={'config': {'private': True }})
</TabPanel> </$Show> </Tabs>

4. Set up authorization

Since we're using a private channel, you need to create a basic RLS policy on the realtime.messages table to allow authenticated users to connect. Row Level Security (RLS) policies control who can access your Realtime channels based on user authentication and custom rules:

sql
-- Allow authenticated users to receive broadcasts
CREATE POLICY "authenticated_users_can_receive" ON realtime.messages
  FOR SELECT TO authenticated USING (true);

-- Allow authenticated users to send broadcasts
CREATE POLICY "authenticated_users_can_send" ON realtime.messages
  FOR INSERT TO authenticated WITH CHECK (true);

5. Send and receive messages

There are three main ways to send messages with Realtime:

5.1 using client libraries

Send and receive messages using the Supabase client:

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
ts
// Listen for messages
channel
  .on('broadcast', { event: 'message_sent' }, (payload: { payload: any }) => {
    console.log('New message:', payload.payload)
  })
  .subscribe()

// Send a message
channel.send({
  type: 'broadcast',
  event: 'message_sent',
  payload: {
    text: 'Hello, world!',
    user: 'john_doe',
    timestamp: new Date().toISOString(),
  },
})
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
dart
// Listen for messages
channel.onBroadcast(
  event: 'message_sent',
  callback: (payload) {
    print('New message: ${payload['payload']}');
  },
).subscribe();

// Send a message
channel.sendBroadcastMessage(
  event: 'message_sent',
  payload: {
    'text': 'Hello, world!',
    'user': 'john_doe',
    'timestamp': DateTime.now().toIso8601String(),
  },
);
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
// Listen for messages
await channel.onBroadcast(event: "message_sent") { message in
  print("New message: \(message.payload)")
}

let status = await channel.subscribe()

// Send a message
await channel.sendBroadcastMessage(
  event: "message_sent",
  payload: [
    "text": "Hello, world!",
    "user": "john_doe",
    "timestamp": ISO8601DateFormatter().string(from: Date())
  ]
)
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python">
python
# Listen for messages
def message_handler(payload):
    print(f"New message: {payload['payload']}")

channel.on_broadcast(event="message_sent", callback=message_handler).subscribe()

# Send a message
channel.send_broadcast_message(
    event="message_sent",
    payload={
        "text": "Hello, world!",
        "user": "john_doe",
        "timestamp": datetime.now().isoformat()
    }
)
</TabPanel> </$Show> </Tabs>

5.2 using HTTP/REST API

Send messages via HTTP requests, perfect for server-side applications:

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
ts
// Send message via REST API
const response = await fetch(`https://<project>.supabase.co/rest/v1/rpc/broadcast`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer <your-service-role-key>`,
    apikey: '<your-service-role-key>',
  },
  body: JSON.stringify({
    topic: 'room:lobby:messages',
    event: 'message_sent',
    payload: {
      text: 'Hello from server!',
      user: 'system',
      timestamp: new Date().toISOString(),
    },
    private: true,
  }),
})
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
dart
import 'package:http/http.dart' as http;
import 'dart:convert';

// Send message via REST API
final response = await http.post(
  Uri.parse('https://<project>.supabase.co/rest/v1/rpc/broadcast'),
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer <your-service-role-key>',
    'apikey': '<your-service-role-key>',
  },
  body: jsonEncode({
    'topic': 'room:lobby:messages',
    'event': 'message_sent',
    'payload': {
      'text': 'Hello from server!',
      'user': 'system',
      'timestamp': DateTime.now().toIso8601String(),
    },
    'private': true,
  }),
);
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
import Foundation

// Send message via REST API
let url = URL(string: "https://<project>.supabase.co/rest/v1/rpc/broadcast")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer <your-service-role-key>", forHTTPHeaderField: "Authorization")
request.setValue("<your-service-role-key>", forHTTPHeaderField: "apikey")

let payload = [
  "topic": "room:lobby:messages",
  "event": "message_sent",
  "payload": [
    "text": "Hello from server!",
    "user": "system",
    "timestamp": ISO8601DateFormatter().string(from: Date())
  ],
  "private": true
] as [String: Any]

request.httpBody = try JSONSerialization.data(withJSONObject: payload)

let (data, response) = try await URLSession.shared.data(for: request)
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python">
python
import requests
from datetime import datetime

# Send message via REST API
response = requests.post(
    'https://<project>.supabase.co/rest/v1/rpc/broadcast',
    headers={
        'Content-Type': 'application/json',
        'Authorization': 'Bearer <your-service-role-key>',
        'apikey': '<your-service-role-key>'
    },
    json={
        'topic': 'room:lobby:messages',
        'event': 'message_sent',
        'payload': {
            'text': 'Hello from server!',
            'user': 'system',
            'timestamp': datetime.now().isoformat()
        },
        'private': True
    }
)
</TabPanel> </$Show> </Tabs>

5.3 using database triggers

Automatically broadcast database changes using triggers. Choose the approach that best fits your needs:

Using realtime.broadcast_changes (Best for mirroring database changes)

sql
-- Create a trigger function for broadcasting database changes
CREATE OR REPLACE FUNCTION broadcast_message_changes()
RETURNS TRIGGER AS $$
BEGIN
  -- Broadcast to room-specific channel
  PERFORM realtime.broadcast_changes(
    'room:' || NEW.room_id::text || ':messages',
    TG_OP,
    TG_OP,
    TG_TABLE_NAME,
    TG_TABLE_SCHEMA,
    NEW,
    OLD
  );
  RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Apply trigger to your messages table
CREATE TRIGGER messages_broadcast_trigger
  AFTER INSERT OR UPDATE OR DELETE ON messages
  FOR EACH ROW EXECUTE FUNCTION broadcast_message_changes();

Using realtime.send (Best for custom notifications and filtered data)

sql
-- Create a trigger function for custom notifications
CREATE OR REPLACE FUNCTION notify_message_activity()
RETURNS TRIGGER AS $$
BEGIN
  -- Send custom notification when new message is created
  IF TG_OP = 'INSERT' THEN
    PERFORM realtime.send(
      jsonb_build_object(
        'message_id', NEW.id,
        'user_id', NEW.user_id,
        'room_id', NEW.room_id,
        'created_at', NEW.created_at
      ),
      'message_created',
      'room:' || NEW.room_id::text || ':notifications',
      true  -- private channel
    );
  END IF;

  RETURN NULL;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Apply trigger to your messages table
CREATE TRIGGER messages_notification_trigger
  AFTER INSERT ON messages
  FOR EACH ROW EXECUTE FUNCTION notify_message_activity();
  • realtime.broadcast_changes sends the full database change with metadata
  • realtime.send allows you to send custom payloads and control exactly what data is broadcast

Essential best practices

Use private channels

Always use private channels for production applications to ensure proper security and authorization:

ts
const channel = supabase.channel('room:123:messages', {
  config: { private: true },
})

Follow naming conventions

Channel Topics: Use the pattern scope:id:entity

  • room:123:messages - Messages in room 123
  • game:456:moves - Game moves for game 456
  • user:789:notifications - Notifications for user 789

Clean up subscriptions

Always unsubscribe when you are done with a channel to ensure you free up resources:

<Tabs scrollable size="small" type="underlined" defaultActiveId="ts" queryGroup="language"

<TabPanel id="ts" label="TypeScript">
ts
// React example
import { useEffect } from 'react'

useEffect(() => {
  const channel = supabase.channel('room:123:messages')

  return () => {
    supabase.removeChannel(channel)
  }
}, [])
</TabPanel> <$Show if="sdk:dart"> <TabPanel id="dart" label="Flutter">
dart
// Flutter example
class _MyWidgetState extends State<MyWidget> {
  RealtimeChannel? _channel;

  @override
  void initState() {
    super.initState();
    _channel = supabase.channel('room:123:messages');
  }

  @override
  void dispose() {
    _channel?.unsubscribe();
    super.dispose();
  }
}
</TabPanel> </$Show> <$Show if="sdk:swift"> <TabPanel id="swift" label="Swift">
swift
// SwiftUI example
struct ContentView: View {
  @State private var channel: RealtimeChannelV2?

  var body: some View {
    // Your UI here
    .onAppear {
      channel = supabase.realtimeV2.channel("room:123:messages")
    }
    .onDisappear {
      Task {
        await channel?.unsubscribe()
      }
    }
  }
}
</TabPanel> </$Show> <$Show if="sdk:python"> <TabPanel id="python" label="Python">
python
# Python example with context manager
class RealtimeManager:
    def __init__(self):
        self.channel = None

    def __enter__(self):
        self.channel = supabase.channel('room:123:messages')
        return self.channel

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.channel:
            self.channel.unsubscribe()

# Usage
with RealtimeManager() as channel:
    # Use channel here
    pass
</TabPanel> </$Show> </Tabs>

Choose the right feature

When to use Broadcast

  • Real-time messaging and notifications
  • Custom events and game state
  • Database change notifications (with triggers)
  • High-frequency updates (e.g. Cursor tracking)
  • Most use cases

When to use Presence

  • User online/offline status
  • Active user counters
  • Use minimally due to computational overhead

When to use Postgres Changes

  • Quick testing and development
  • Low amount of connected users

Next steps

Now that you understand the basics, dive deeper into each feature:

Core features

  • Broadcast - Learn about sending messages, database triggers, and REST API usage
  • Presence - Implement user state tracking and online indicators
  • Postgres Changes - Understanding database change listeners (consider migrating to Broadcast)

Security & configuration

  • Authorization - Set up RLS policies for private channels
  • Settings - Configure your Realtime instance for optimal performance

Advanced topics

  • Architecture - Understand how Realtime works under the hood
  • Benchmarks - Performance characteristics and scaling considerations
  • Limits - Usage limits and best practices

Integration guides

Framework examples

Ready to build something amazing? Start with the Broadcast guide to create your first real-time feature!