Интеграция Supabase Realtime для подписки на изменения данных
Supabase Realtime позволяет подписаться на изменения в таблицах PostgreSQL (INSERT, UPDATE, DELETE) и получать их в браузере в реальном времени через WebSocket. Построено поверх PostgreSQL Logical Replication.
Установка
npm install @supabase/supabase-js
Подписка на таблицу
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
// Подписка на все изменения в таблице messages
const channel = supabase
.channel('public:messages')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE или *
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}` // фильтр по значению
},
(payload) => {
if (payload.eventType === 'INSERT') {
setMessages(prev => [...prev, payload.new]);
}
if (payload.eventType === 'UPDATE') {
setMessages(prev =>
prev.map(m => m.id === payload.new.id ? payload.new : m)
);
}
if (payload.eventType === 'DELETE') {
setMessages(prev => prev.filter(m => m.id !== payload.old.id));
}
}
)
.subscribe((status) => {
console.log('Subscription status:', status);
});
// Отписка
return () => { supabase.removeChannel(channel); };
React Hook
function useRealtimeTable<T>(
table: string,
filter?: { column: string; value: string }
) {
const [data, setData] = useState<T[]>([]);
const supabase = useSupabaseClient();
useEffect(() => {
// Первоначальная загрузка
let query = supabase.from(table).select('*');
if (filter) query = query.eq(filter.column, filter.value);
query.then(({ data }) => setData(data ?? []));
// Подписка на изменения
const channel = supabase.channel(`${table}:${filter?.value ?? 'all'}`)
.on('postgres_changes', {
event: '*',
schema: 'public',
table,
filter: filter ? `${filter.column}=eq.${filter.value}` : undefined
}, (payload) => {
setData(prev => {
if (payload.eventType === 'INSERT') return [...prev, payload.new as T];
if (payload.eventType === 'UPDATE')
return prev.map(item => (item as any).id === (payload.new as any).id
? payload.new as T : item);
if (payload.eventType === 'DELETE')
return prev.filter(item => (item as any).id !== (payload.old as any).id);
return prev;
});
})
.subscribe();
return () => { supabase.removeChannel(channel); };
}, [table, filter?.column, filter?.value]);
return data;
}
// Использование
const messages = useRealtimeTable<Message>('messages', {
column: 'room_id',
value: roomId
});
Broadcast — кастомные события
Broadcast не требует изменений в БД:
// Отправить событие всем подписчикам канала
await supabase.channel('cursor-positions').send({
type: 'broadcast',
event: 'cursor-moved',
payload: { x: mouseX, y: mouseY, userId: user.id }
});
// Получить
supabase.channel('cursor-positions')
.on('broadcast', { event: 'cursor-moved' }, ({ payload }) => {
updateCursorPosition(payload.userId, payload.x, payload.y);
})
.subscribe();
Row Level Security
Realtime уважает RLS PostgreSQL — клиент видит только те строки, к которым у него есть SELECT-доступ.
-- Пример RLS: пользователь видит только свои сообщения
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own messages"
ON messages FOR SELECT
USING (auth.uid() = user_id);
Сроки
Базовая подписка + React Hook + RLS — 1–2 дня.







