Supabase Realtime Home Lab Setup — ตั้ง Lab
Supabase Realtime Lab
Supabase Realtime Home Lab Self-hosted PostgreSQL WebSocket Subscription Broadcast Presence RLS Edge Functions Chat Dashboard Collaboration
| Feature | Protocol | Use Case | Latency | Scale |
|---|---|---|---|---|
| Postgres Changes | WebSocket + LISTEN/NOTIFY | Live data updates | 50-200ms | 10K connections |
| Broadcast | WebSocket | Chat, cursor sharing | 10-50ms | 100K messages/s |
| Presence | WebSocket + CRDT | Online users, typing | 100-500ms | 10K users/room |
| Edge Functions | Deno Deploy | Server-side logic | 5-50ms | Auto-scale |
| Auth | JWT + GoTrue | User authentication | 50-200ms | Unlimited |
Self-hosted Setup
=== Supabase Self-hosted with Docker ===
Clone and setup
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env
Edit .env — Important settings
POSTGRES_PASSWORD=your-super-secret-password
JWT_SECRET=your-super-secret-jwt-token-at-least-32-chars
ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
DASHBOARD_USERNAME=admin
DASHBOARD_PASSWORD=admin123
SITE_URL=http://localhost:3000
Start all services
docker compose up -d
Services started:
- PostgreSQL :5432
- Supabase Studio :3000 (Dashboard)
- Kong Gateway :8000 (API)
- GoTrue :9999 (Auth)
- PostgREST :3001 (REST API)
- Realtime :4000 (WebSocket)
- Storage :5000 (File Storage)
- Meta :8080 (Metadata)
Create table with Realtime enabled
CREATE TABLE messages (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id UUID REFERENCES auth.users NOT NULL,
content TEXT NOT NULL,
room TEXT NOT NULL DEFAULT 'general',
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
-- Enable Realtime
ALTER PUBLICATION supabase_realtime ADD TABLE messages;
from dataclasses import dataclass
@dataclass
class ServiceConfig:
service: str
image: str
port: int
resource: str
purpose: str
services = [
ServiceConfig("PostgreSQL", "supabase/postgres", 5432, "2 CPU 4GB RAM", "Database"),
ServiceConfig("Realtime", "supabase/realtime", 4000, "1 CPU 1GB RAM", "WebSocket server"),
ServiceConfig("PostgREST", "postgrest/postgrest", 3001, "0.5 CPU 512MB", "Auto REST API"),
ServiceConfig("GoTrue", "supabase/gotrue", 9999, "0.5 CPU 512MB", "Authentication"),
ServiceConfig("Kong", "kong", 8000, "1 CPU 1GB RAM", "API Gateway"),
ServiceConfig("Studio", "supabase/studio", 3000, "1 CPU 1GB RAM", "Dashboard UI"),
ServiceConfig("Storage", "supabase/storage-api", 5000, "0.5 CPU 512MB", "File storage"),
]
print("=== Supabase Services ===")
for s in services:
print(f" [{s.service}] Port: {s.port} | Image: {s.image}")
print(f" Resource: {s.resource} | Purpose: {s.purpose}")
Realtime Client Code
=== Realtime Subscription Examples ===
JavaScript Client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'http://localhost:8000',
'your-anon-key'
)
1. Postgres Changes — Listen to INSERT
const channel = supabase
.channel('messages-channel')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages',
filter: 'room=eq.general' },
(payload) => {
console.log('New message:', payload.new)
addMessageToUI(payload.new)
}
)
.subscribe()
2. Broadcast — Send/receive custom events
const broadcastChannel = supabase
.channel('cursor-room')
.on('broadcast', { event: 'cursor-move' }, (payload) => {
moveCursor(payload.payload.x, payload.payload.y, payload.payload.user)
})
.subscribe()
Send cursor position
broadcastChannel.send({
type: 'broadcast',
event: 'cursor-move',
payload: { x: 100, y: 200, user: 'alice' }
})
3. Presence — Track online users
const presenceChannel = supabase
.channel('online-users')
.on('presence', { event: 'sync' }, () => {
const state = presenceChannel.presenceState()
updateOnlineUsers(Object.keys(state))
})
.subscribe(async (status) => {
if (status === 'SUBSCRIBED') {
await presenceChannel.track({
user: 'alice', online_at: new Date().toISOString()
})
}
})
Python Client
from supabase import create_client
supabase = create_client("http://localhost:8000", "your-anon-key")
def handle_change(payload):
print(f"Change: {payload}")
channel = supabase.channel("messages")
channel.on_postgres_changes(
event="INSERT",
schema="public",
table="messages",
callback=handle_change
).subscribe()
@dataclass
class RealtimeFeature:
feature: str
event_types: str
use_case: str
example: str
features = [
RealtimeFeature("Postgres Changes", "INSERT UPDATE DELETE",
"Live data sync, dashboard updates",
"Chat messages, order status, stock prices"),
RealtimeFeature("Broadcast", "Custom events",
"Peer-to-peer messaging, no persistence",
"Cursor sharing, typing indicator, game state"),
RealtimeFeature("Presence", "sync join leave",
"Track online users, user state",
"Online indicators, collaborative editing"),
]
print("\n=== Realtime Features ===")
for f in features:
print(f" [{f.feature}] Events: {f.event_types}")
print(f" Use case: {f.use_case}")
print(f" Example: {f.example}")
RLS and Security
=== Row Level Security Policies ===
-- Users can read messages in rooms they belong to
CREATE POLICY "Users can read room messages"
ON messages FOR SELECT
USING (
room IN (
SELECT room_id FROM room_members
WHERE user_id = auth.uid()
)
);
-- Users can insert their own messages
CREATE POLICY "Users can insert own messages"
ON messages FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- Users can update their own messages
CREATE POLICY "Users can update own messages"
ON messages FOR UPDATE
USING (auth.uid() = user_id);
-- Users can delete their own messages
CREATE POLICY "Users can delete own messages"
ON messages FOR DELETE
USING (auth.uid() = user_id);
-- Public read for announcements
CREATE POLICY "Anyone can read announcements"
ON announcements FOR SELECT
USING (true);
Edge Function — Server-side validation
supabase/functions/validate-message/index.ts
import { serve } from 'https://deno.land/std/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js'
serve(async (req) => {
const { content, room } = await req.json()
if (content.length > 1000) {
return new Response('Message too long', { status: 400 })
}
Moderate content
if (containsBadWords(content)) {
return new Response('Inappropriate content', { status: 400 })
}
const supabase = createClient(
Deno.env.get('SUPABASE_URL'),
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
)
const { data, error } = await supabase
.from('messages')
.insert({ content, room, user_id: req.auth.uid })
return new Response(JSON.stringify(data))
})
@dataclass
class SecurityCheck:
check: str
status: str
fix: str
checklist = [
SecurityCheck("RLS enabled on all tables", "REQUIRED",
"ALTER TABLE x ENABLE ROW LEVEL SECURITY"),
SecurityCheck("RLS policies defined", "REQUIRED",
"CREATE POLICY for SELECT INSERT UPDATE DELETE"),
SecurityCheck("JWT Secret rotated", "RECOMMENDED",
"Change JWT_SECRET in .env, restart services"),
SecurityCheck("Service Role Key protected", "REQUIRED",
"Never expose in client code, use only server-side"),
SecurityCheck("Realtime filtered by RLS", "AUTOMATIC",
"Realtime respects RLS policies automatically"),
SecurityCheck("Rate limiting configured", "RECOMMENDED",
"Kong rate-limiting plugin, 100 req/min per user"),
SecurityCheck("HTTPS enabled", "REQUIRED for production",
"Use reverse proxy with SSL certificate"),
]
print("Security Checklist:")
for c in checklist:
print(f" [{c.status}] {c.check}")
print(f" Fix: {c.fix}")
เคล็ดลับ
- RLS: เปิด RLS ทุก Table ก่อน Enable Realtime ป้องกัน Data Leak
- Filter: ใช้ Filter ใน Subscription ฟังเฉพาะข้อมูลที่ต้องการ
- Presence: อย่าเก็บข้อมูลมากใน Presence State เก็บเฉพาะที่จำเป็น
- Unsubscribe: อย่าลืม Unsubscribe เมื่อ Component Unmount
- Backup: ตั้ง pg_dump Backup PostgreSQL ทุกวัน
Supabase Realtime คืออะไร
Real-time Client Database PostgreSQL LISTEN NOTIFY WebSocket Postgres Changes Broadcast Presence Chat Dashboard Collaboration Notification