Expo × Supabase × React Native でリアルタイムチャットを作ってみた
はじめに
この作品は、別プロジェクトでリアルタイムチャットの機能が必要になった際に、まず作ってみたサンプルです。
実際にこの構成をもとに、既読処理、メッセージの削除、「〇〇が参加しました」といったアラート、プッシュ通知の実装まで行うことができました。
そのプロジェクトが一区切りついたあと、このサンプルは数ヶ月ほど眠っていたのですが、せっかくなので今回を機に紹介しておこうと思います。
使用技術
- React Native (Expo + TypeScript)
- Supabase(Database + Realtime)
- AsyncStorage(ユーザー名保存)
- Day.js(時刻整形)
アプリの画面イメージ
ユーザー名入力画面
チャットルーム画面(iOS / Android / Web)
iOS
Android
Web
💾 チャットデータの構成
チャットのメッセージは Supabase 上の messages
テーブルに保存されます。
このテーブルは、ユーザー名・本文・送信時刻の3つを基本要素とする、シンプルな構成です。
messages
テーブルのSQL定義
CREATE TABLE messages (
id serial PRIMARY KEY,
username text NOT NULL,
content text NOT NULL,
created_at timestamp with time zone DEFAULT timezone('utc', now())
);
🔍 各カラムの解説
カラム名 | 型 | 説明 |
---|---|---|
id |
serial |
メッセージの一意なID(自動採番) |
username |
text |
投稿者の名前(ユーザーが任意に入力) |
content |
text |
メッセージ本文 |
created_at |
timestamp with time zone |
投稿日時(デフォルトでUTCの現在時刻) |
※ Supabase の Realtime を利用するため、このテーブルに対して「Realtimeを有効化」しておく必要があります。
🔸 メッセージオブジェクトの構造
クライアントアプリでは、Supabaseから取得した各メッセージを以下のようなオブジェクトとして扱います。
type Message = {
id: number; // メッセージの一意なID(自動的に割り当てられる)
username: string; // 投稿者の名前(任意に入力されたもの)
content: string; // メッセージ本文(送信されたテキスト)
created_at: string; // 投稿日時(ISO形式の文字列、例: "2025-06-27T14:02:45.000Z")
};
たとえば、1件のメッセージデータは次のようになります。
{
"id": 1,
"username": "テストユーザー",
"content": "こんにちは!",
"created_at": "2025-06-27T14:02:45.000Z"
}
これらのデータを時刻順に並べてリスト表示し、チャットUIとして描画しています。
✉️ メッセージの送信
チャット画面の下部にあるテキスト入力欄にメッセージを入力し、送信ボタン(✈️)をタップすると、その内容が Supabase に保存されます。これにより、他のクライアントにもリアルタイムでメッセージが共有される仕組みです。
🔁 送信処理の流れ
- 入力欄にメッセージを入力
- 「✈️」ボタンを押すと、
handleSendMessage
関数が実行 - Supabase の
messages
テーブルにinsert()
で追加 - 入力欄が空になり、画面が自動スクロールされる
// 送信関数の一部抜粋
const handleSendMessage = async () => {
if (message.trim()) {
const { error } = await supabase.from("messages").insert([{
username,
content: message,
created_at: new Date().toISOString(),
}]);
if (error) {
console.error("Error sending message:", error);
}
setMessage("");
setInputHeight(40);
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: true });
}, 100);
}
};
-
created_at
はクライアントで付与していますが、DB側でもデフォルト値が設定されているためどちらでも対応可能です。 -
.trim()
によって空白だけの送信を防止。 -
FlatList
を使って最新の位置までスクロール。
📦 過去メッセージの取得(初期ロード)
チャット画面が表示されたとき、まず Supabase の messages
テーブルから、既存のメッセージを取得して一覧として表示します。
const loadMessages = async () => {
try {
const { data, error } = await supabase
.from("messages")
.select("*")
.order("created_at", { ascending: true });
if (error) throw error;
if (data) {
setMessages(data);
setTimeout(() => {
flatListRef.current?.scrollToEnd({ animated: true });
}, 100);
}
} catch (error) {
console.error("Failed to load messages", error);
}
};
- 初回取得時は
created_at
の昇順で並べ替え、時系列で表示。 - ロード後に下部スクロールで最新メッセージがすぐ見える。
📡 リアルタイム購読(新着メッセージの受信)
Supabase の Realtime を使うことで、他のユーザーが送信したメッセージを自動的に受信して表示できます。
これは insert()
によってメッセージが送信されるたびに発火する仕組みで、ユーザーに更新を意識させることなくチャットが成立します。
useEffect(() => {
loadMessages();
const subscription = supabase
.from("messages")
.on("INSERT", (payload) => {
const newMessage = payload.new as Message;
setMessages((prev) => [...prev, newMessage]);
})
.subscribe();
return () => {
supabase.removeSubscription(subscription);
};
}, []);
-
.on("INSERT")
によって、他ユーザーのinsert()
を監視。 -
payload.new
に含まれるメッセージを配列に追加。 -
subscribe()
開始とremoveSubscription()
で適切に管理。
プロジェクトの実行
Githubからプロジェクトのクローン
git clone https://github.com/Gratien583/tsx-Supabase-ChatApp-Beta.git
cd tsx-Supabase-ChatApp-Beta
🔧 Supabase 設定
このアプリを動作させるには、.env
ファイルに Supabase プロジェクトの URL と API キーを設定してください。
1. Supabase プロジェクトの作成とキーの取得
- https://app.supabase.com/ にアクセスし、新規プロジェクトを作成します。
- 「Project URL」と「anon key(APIキー)」を取得します。
.env
ファイルの作成
2. プロジェクトルートに .env
ファイルを作成し、以下のように記述します:
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_KEY=your-anon-key
※
.env
は.gitignore
に含め、公開しないよう注意してください。
supabaseClient.ts
の内容
3. import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.EXPO_PUBLIC_SUPABASE_KEY;
if (!supabaseUrl || !supabaseKey) {
throw new Error("Supabase環境変数が設定されていません。");
}
export const supabase = createClient(supabaseUrl, supabaseKey);
依存関係のインストール
npm install
Expoにて実行
npx expo start
終わりに
本記事のチャットアプリは、あるチーム開発の下準備として急ごしらえで作ったものです。
ですが、個人的に意外と使い勝手がよく、プロジェクトの土台としてかなり活躍してくれました。
技術的にすごい仕組みは使っていませんが、「まず動くものを作る → 機能を増やす」サイクルを体感できた良い経験でした。
Discussion