mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
* fix(docs): add missing variables to api_settings partial in realtime getting_started * Update apps/docs/content/guides/realtime/getting_started.mdx --------- Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>
704 lines
16 KiB
Plaintext
704 lines
16 KiB
Plaintext
---
|
|
id: 'getting-started'
|
|
title: 'Getting Started with Realtime'
|
|
description: 'Learn how to build real-time applications with Supabase Realtime'
|
|
subtitle: 'Learn how to build real-time applications with Supabase Realtime'
|
|
sidebar_label: 'Getting Started'
|
|
---
|
|
|
|
## 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(
|
|
'room:' || NEW.room_id::text || ':notifications',
|
|
'message_created',
|
|
jsonb_build_object(
|
|
'message_id', NEW.id,
|
|
'user_id', NEW.user_id,
|
|
'room_id', NEW.room_id,
|
|
'created_at', NEW.created_at
|
|
),
|
|
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](/docs/guides/realtime/broadcast)** - Learn about sending messages, database triggers, and REST API usage
|
|
- **[Presence](/docs/guides/realtime/presence)** - Implement user state tracking and online indicators
|
|
- **[Postgres Changes](/docs/guides/realtime/postgres-changes)** - Understanding database change listeners (consider migrating to Broadcast)
|
|
|
|
### Security & configuration
|
|
|
|
- **[Authorization](/docs/guides/realtime/authorization)** - Set up RLS policies for private channels
|
|
- **[Settings](/docs/guides/realtime/settings)** - Configure your Realtime instance for optimal performance
|
|
|
|
### Advanced topics
|
|
|
|
- **[Architecture](/docs/guides/realtime/architecture)** - Understand how Realtime works under the hood
|
|
- **[Benchmarks](/docs/guides/realtime/benchmarks)** - Performance characteristics and scaling considerations
|
|
- **[Quotas](/docs/guides/realtime/quotas)** - Usage limits and best practices
|
|
|
|
### Integration guides
|
|
|
|
- **[Realtime with Next.js](/docs/guides/realtime/realtime-with-nextjs)** - Build real-time Next.js applications
|
|
- **[User Presence](/docs/guides/realtime/realtime-user-presence)** - Implement user presence features
|
|
- **[Database Changes](/docs/guides/realtime/subscribing-to-database-changes)** - Listen to database changes
|
|
|
|
### Framework examples
|
|
|
|
- **[Flutter Integration](/docs/guides/realtime/realtime-listening-flutter)** - Build real-time Flutter applications
|
|
|
|
Ready to build something amazing? Start with the [Broadcast guide](/docs/guides/realtime/broadcast) to create your first real-time feature!
|