Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to…

Follow publication

Realtime Chat With Supabase Realtime is Supa-easy

--

When you think of Supabase you probably think of their easy to use database, but they have realtime eventing system built-in as well. When I saw that I knew I had to build a chat system using it with React and NextJS and as it turns out it was supa-easy. (Sorry, I had to.)

You can try out the project by pulling my github repo and dropping in your Supabase URL and client safe key into .env.development.local.

Article thumbnail with two chat sessions talking about how slick the chatting is

I often get asked about realtime support with the NextJS App Router; if it’s supported or not, how to use it. Now sure, you can set up your own sockets support and all that, but for simplicity I like to go with something out of the box and that’s just what Supabase provides.

Setup

I kept the project setup on this NextJS simple. I went with all the stock defaults (TypeScript, eslint, etc.) but importantly Tailwind. And on top of that I added shadcn for the button and input components.

After that it was off to Supabase where I registered an account against my github. Once I logged in I created a project and got a project URL and client safe key. Then I added those to my project as NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_KEY . I had to add the NEXT_PUBLIC_ prefix to make sure these environment variables got out to the client code. Because the client code is going to be doing all the work in this case.

Ah yes, I’m going to be doing this on the client. Which begs the question, why use NextJS if I’m not going to leverage the server? Fair enough. The reason is that I really don’t expect that you are going to be using this system to build a chat mechanisms, there are better ones off the shelf. I’m just showing you an eventing model and how to use it on the client. But the server can also join the stream to send and recieve events as well. I’ll let you daydream about the possibilities of that as we move on to installing and using Supabase.

My last setup piece was to bring in the @supabase/supabase-js library.

And that was it for setup. Next up we create a client component for our chat.

Connecting To The Channel

Creating and connect to a Supabase channel in a React component is remarkably easy.

  const channel = useRef<RealtimeChannel | null>(null);

useEffect(() => {
if (!channel.current) {
const client = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_KEY!
);

channel.current = client.channel("chat-room", {
config: {
broadcast: {
self: true,
},
},
});

channel.current
.on("broadcast", { event: "message" }, ({ payload }) => {
setMessages((prev) => [...prev, payload.message]);
})
.subscribe();
}

return () => {
channel.current?.unsubscribe();
channel.current = null;
};
}, []);

We use a ref to store the channel because we’ll use it next to send messages. Then we’ll set up the client and the channel in a useEffect. We’ll use an effect because we only want that to run on the client, and useEffect doesn’t run on the server.

Inside the useEffect we’ll first create a client for Supabase using the keys in our environment. With our client created we can then use that to create a channel. I’ve called it chat-room but you can call it whatever you like.

I’ve configured the channel to broadcast messages back to my self , which means the current client, so that I see my own messages and add them to the list of messages.

After I’ve created the channel I set myself up to receive messages using the on method. And finally I initiate the subscription with subscribe .

And of course I’m a good citizen of React-o-verse because I also setup an unsubscribe when the component gets un-mounted.

Sending And Receiving Messages

Our little chat application holds the current user and message, as well as the list of messages, in component state using useState .

  const [message, setMessage] = useState<string>("");
const [user, setUser] = useState<string>("Jane");
const [messages, setMessages] = useState<
{
user: string;
message: string;
}[]
>([]);

You could use a state manager for this if you want to, but I think it’s way overkill in this case.

To send a message we have an onSend function that is called when you hit the Send button, or you hit Enter on the message text.


function onSend() {
if (!channel.current || message.trim().length === 0) return;
channel.current.send({
type: "broadcast",
event: "message",
payload: { message: { message, user } },
});
setMessage("");
}

It’s hard to get easier than that! We use the send method on the channel to send our message text and user name as the payload of the message

And we’ve got that message receiving code already set up in the useEffect :

      channel.current
.on("broadcast", { event: "message" }, ({ payload }) => {
setMessages((prev) => [...prev, payload.message]);
})

So now whever a message comes in, whether it’s from us or someone else, we just add it to the list of messages and send it out.

Putting A Face On It

The rest of the component code is just putting a nice face on it.

  return (
<>
<div className="flex gap-2">
<Input
type="text"
placeholder="User"
value={user}
onChange={(e) => setUser(e.target.value)}
className="flex-[0.2] text-2xl"
/>
<Input
type="text"
placeholder="Message"
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyUp={(e) => {
if (e.key === "Enter") {
onSend();
}
}}
className="flex-[0.7] text-2xl"
/>
<Button onClick={onSend} className="text-2xl">
Send
</Button>
</div>

<div className="mt-5 flex flex-col gap-3">
{messages.map((msg, i) => (
<div
key={i}
className={`p-3 rounded-lg w-2/3 text-2xl bg-${
user === msg.user ? "blue-800" : "gray-600"
} ${user === msg.user ? "self-end" : "self-start"}`}
>
{msg.message}
</div>
))}
</div>
</>
);

In the top section we have Shadcn Input components for the user name and message. And we have a Button that will send the message.

In the second section we simply format the messages. If the message is from us we put it on the right, if it’s from someone else we put it on the left.

And that’s all there is! Check it out for yourself! Fire it up and give it a try then put your own spin on it.

It’s Not Just A Database

I mentioned before that I get asked about realtime support in NextJS a lot. But I also get asked to cover Supabase a whole lot. I’ve been neglectful about that and I’ve definitely fallen into the trap of thinking about Supabase as just a database. I mean, c’mon, it’s Supa-base. But there is so much more to it. They’ve got authentication. Vector database support for you RAG fans out there. There is object storage if you need to host images or other assets from your customers. And… it’s got this sick realtime feature, which is crazy easy to use!

Stackademic 🎓

Thank you for reading until the end. Before you go:

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Published in Stackademic

Stackademic is a learning hub for programmers, devs, coders, and engineers. Our goal is to democratize free coding education for the world.

Written by Jack Herrington

YouTuber and Principal Engineer. Full NextJS course at pronextjs.dev

Responses (2)

Write a response