Back to roadmaps supabase Course

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: