Project: Realtime Chat Message Board
In this project, we will build a real-time message board. When a user submits a message, it is broadcast to all other connected users instantly via WebSockets without needing a page refresh.
1. Enabling Realtime Replication
By default, Supabase does not broadcast database changes to save database performance. You must enable the real-time publisher channel on your target database table:
-- Enable Realtime replication on the messages table
ALTER PUBLICATION supabase_realtime ADD TABLE messages;2. Implementing the React Component
Here is the React chat interface component:
// src/components/RealtimeChat.tsx
import React, { useState, useEffect } from "react";
import { supabase } from "../lib/supabase";
interface Message {
id: number;
content: string;
sender_name: string;
created_at: string;
}
export default function RealtimeChat() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [senderName, setSenderName] = useState("Anonymous");
useEffect(() => {
// 1. Fetch initial message history
loadMessages();
// 2. Setup Realtime subscription to listen for INSERT changes
const channel = supabase
.channel("public:messages")
.on(
"postgres_changes",
{ event: "INSERT", schema: "public", table: "messages" },
(payload) => {
const newMessage = payload.new as Message;
setMessages((prev) => [...prev, newMessage]);
}
)
.subscribe();
// 3. Clean up subscription on component unmount
return () => {
supabase.removeChannel(channel);
};
}, []);
async function loadMessages() {
const { data } = await supabase
.from("messages")
.select("*")
.order("created_at", { ascending: true });
if (data) setMessages(data);
}
async function sendMessage(e: React.FormEvent) {
e.preventDefault();
if (!input.trim()) return;
// Insert message into the PostgreSQL table
const { error } = await supabase
.from("messages")
.insert([{ content: input, sender_name: senderName }]);
if (error) {
console.error("Failed to send message:", error.message);
} else {
setInput("");
}
}
return (
<div className="max-w-lg mx-auto border rounded-xl overflow-hidden bg-white shadow-sm flex flex-col h-[500px] mt-10">
<div className="bg-blue-600 p-4 text-white font-bold">
<h3>Realtime Chatroom</h3>
</div>
{/* Messages list */}
<div className="flex-1 p-4 overflow-y-auto space-y-4">
{messages.map((msg) => (
<div key={msg.id} className="p-3 bg-gray-50 rounded-lg max-w-[85%]">
<p className="text-xs text-blue-600 font-semibold">{msg.sender_name}</p>
<p className="text-gray-900 mt-1">{msg.content}</p>
</div>
))}
</div>
{/* Send message form */}
<form onSubmit={sendMessage} className="p-4 border-t flex gap-2">
<input
type="text"
placeholder="Your name"
value={senderName}
onChange={(e) => setSenderName(e.target.value)}
className="w-1/4 border p-2 rounded-lg text-sm"
/>
<input
type="text"
placeholder="Type your message..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="flex-1 border p-2 rounded-lg text-sm"
/>
<button type="submit" className="bg-blue-600 text-white px-5 rounded-lg text-sm hover:bg-blue-700">
Send
</button>
</form>
</div>
);
}Published on Last updated: