Supabase Realtimeで作るリアルタイムランキング
はじめに
ゲームやスコア集計のように、ランキングがリアルタイムで変動する機能はよく求められます。
従来は WebSocket サーバーを自前で立てたり、ポーリングで定期取得する方法が主流でしたが、Supabase Realtime を使えば簡単に実現できます。
アプリ紹介:カルタタイピング
この記事では、私が開発している 「タイピング」アプリ にリアルタイムランキングを実装した事例を紹介します。
群馬の郷土かるた「上毛かるた」を題材にしたタイピングゲーム カルタタイピング です。
読み札を正しくタイピングすると札が取れる、というルールをブラウザで再現しています。
このアプリに「リアルタイムランキング」を導入し、ユーザーのスコアが即座に反映される仕組みを作りました
Supabase Realtimeとは
Supabase Realtime には3つの機能があります。
- Broadcast: クライアント同士で任意のイベントを送受信
- Presence: ユーザーのオンライン/オフライン状態を同期
- Postgres Changes: データベースの変更を購読して通知
今回は、テーブルの更新イベントを購読できる Postgres Changes
を利用します。これにより、DBのスコア更新をそのままフロントに反映できます。
実装の全体像
- スコアは PostgreSQL に保存
- Supabase Realtime でスコアテーブルの変更を購読
- フロントエンド(今回はSvelteKitを使用しました)でランキング表示を更新
ユーザー入力 → DB(supabase) → Realtime通知 → フロントで反映
テーブル定義
まずはスコアを保存するシンプルなテーブルを作成します。
create table scores (
id uuid primary key default gen_random_uuid(),
user_name text not null,
score int not null,
created_at timestamp with time zone default now()
);
Realtime購読の実装
次に、フロントから Supabase Realtime を購読します。
Supabase側のRealtime購読実装(src/lib/services/supabaseService.ts)です。
// 購読を開始する実装
export function subscribeToTopScoreChanges(
callback: (scores: any[]) => void,
difficulty: 'beginner' | 'standard' | 'advanced',
limit: number = 100
): RealtimeChannel {
const channel = supabase.channel('score-changes')
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'Score'
},
async () => {
// Scoreテーブルが更新された際に発火する処理
const updatedScores = await getTopScoresByDifficulty(difficulty, limit);
callback(updatedScores);
}
)
.subscribe();
return channel;
}
// 購読を終了する実装
export function unsubscribeFromTopScoreChanges(channel: RealtimeChannel) {
supabase.removeChannel(channel);
}
これで INSERT や UPDATE が発生すると、getTopScoresByDifficulty(difficulty, limit)が走り、最新のランキングを昇順で取得することができます。
ランキングの画面実装処理
ランキング画面(src/routes/ranking/+page.svelte)の実装内容です。
onMount(async () => {
await loadRankings();
setupRealtimeSubscription();
});
onDestroy(() => {
if (realtimeChannel) {
unsubscribeFromTopScoreChanges(realtimeChannel);
}
});
async function loadRankings() {
try {
loading = true;
error = null;
let data;
data = await getTopScoresByDifficulty(selectedDifficulty, 100);
rankings = data as RankingEntry[];
} catch (err) {
error = 'ランキングの読み込みに失敗しました';
console.error('Failed to load rankings:', err);
} finally {
loading = false;
}
}
function setupRealtimeSubscription() {
const topNumber = 100;
try {
realtimeChannel = subscribeToTopScoreChanges((updatedScores) => {
rankings = updatedScores as RankingEntry[];
}, selectedDifficulty, topNumber);
} catch (err) {
console.warn('Realtime subscription failed:', err);
}
}
ランキング画面に遷移した際に
- loadRankings()によって、ランキングを取得
- setupRealtimeSubscription()によって購読を開始します。
ランキング画面から離脱した際は
- unsubscribeFromTopScoreChanges(realtimeChannel)によって、購読を終了します。
これでランキングは常に最新のスコアに基づいてリアルタイム更新されます。
実際の挙動 Before / After:リアルタイム化の効果
実装前後の比較動画です。
Before
- ランキングは更新されるが、ページをリロードしないと反映されない
- ユーザーがスコアを入力しても、他の参加者にはすぐに伝わらない
After
- スコアが更新されると、即座にランキングに反映
- 他のユーザーのスコア変化もリアルタイムで追える
まとめ
Supabase Realtime の Postgres Changes を使えば、リアルタイムランキングを簡単に実装できる
WebSocket サーバーを自前で構築する必要がないので、実装コストも低い
ゲームのスコア表示だけでなく、ダッシュボードやリアルタイム集計にも応用可能
Supabase の Realtime は、「DB更新をそのままフロントへ」 という強力な仕組みです。
今後は Presence を組み合わせて「誰がオンラインか」まで見せられるようにすると、さらにリッチなリアルタイムアプリが作れるでしょう。
Discussion