Build A Real-Time Chat App With Supabase & Next.js
Hey guys! Ever wanted to build your own real-time chat application? You know, the kind where messages pop up instantly, and everyone's always in the loop? Well, you're in luck! Today, we're diving headfirst into building a Supabase Realtime Chat app using Next.js, a super popular framework for building modern web apps. It's gonna be a fun ride, and by the end, you'll have a working chat application to show off. So, grab your coffee (or your favorite beverage), and let's get started!
Building a real-time chat app from scratch can seem daunting, but with Supabase and Next.js, it's actually pretty manageable. Supabase provides the backend infrastructure, handling all the database stuff, authentication, and, most importantly, the real-time functionality. Next.js handles the frontend, giving us a smooth and efficient way to build the user interface and manage the chat experience. This combination is a powerful one, allowing us to focus on the features of our chat app rather than getting bogged down in server configuration or complex real-time implementations. We're going to cover everything from setting up our project to displaying messages in real time. We will be working on setting up the database, setting up the user interface. We will be working on setting up the real time chat and displaying the messages to other users. You'll be amazed at how quickly you can go from zero to a fully functional chat application.
Setting Up Your Development Environment
First things first, let's get our development environment ready. You'll need Node.js and npm (or yarn) installed on your machine. If you don't have them, go ahead and download the latest versions from the Node.js website. Next, create a new Next.js project. Open your terminal and run the following command: npx create-next-app@latest my-realtime-chat. Replace my-realtime-chat with whatever name you want to give your project. This command will set up a basic Next.js project for us. Navigate into your project directory using cd my-realtime-chat. Now, you're ready to start coding! Make sure to install the required dependencies with either npm install or yarn install to ensure that our project dependencies are correctly installed. This step will pull in all the necessary packages for our project, including React, Next.js, and any other libraries we might use.
We also need to install the Supabase client library, which will allow our Next.js application to interact with our Supabase backend. Run npm install @supabase/supabase-js or yarn add @supabase/supabase-js. This command installs the official Supabase JavaScript client, which provides all the necessary methods for interacting with your Supabase project. For the design and UI components, you can use any styling framework. For example, you can use Tailwind CSS to make the UI look good and customize it with ease. Installing Tailwind CSS is super easy, just follow their official guide, and you are set. This will allow you to quickly and easily style your chat application, giving it a modern and clean look. With our dependencies set up, we will be able to start our project.
Creating a Supabase Project
Now, let's head over to Supabase and create a new project. Go to the Supabase website and sign up for an account if you don't already have one. Once you're logged in, create a new project. Give your project a name and select your preferred region. After creating the project, you'll be taken to your project dashboard. Here, you'll find all the information you need to connect your Next.js application to your Supabase backend. The dashboard gives you access to a ton of features and tools for managing your database, authentication, and real-time functionality. Supabase provides a great user experience, so you won't get lost in the sea of settings.
In the Supabase dashboard, go to the SQL editor. This is where we will create the database schema for our chat app. We need a table to store our chat messages. We can name the table as messages. Here's a basic SQL command to create the messages table:
CREATE TABLE messages (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users(id),
username TEXT,
content TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now())
);
This SQL statement defines a table named messages with the following columns: id (primary key), user_id (foreign key referencing the auth.users table), username, content, and created_at. This table will store all the chat messages, their senders, and the timestamps. Don't worry if this SQL code seems a bit confusing; the key is understanding that it sets up the structure for storing messages. The auth.users table stores user information, automatically managed by Supabase Authentication. It means that we can directly use the user’s ID and username without manually storing and managing that. Remember to save this schema to create the table in your Supabase database. You can add more columns if you want. After creating the table, we'll configure Realtime. This is the magic that enables the instant message updates. In the Supabase dashboard, go to the Realtime settings and enable Realtime for your messages table. This tells Supabase to listen for changes in the messages table and send those changes to connected clients in real time. This setting is crucial for the core functionality of our chat app.
Connecting Next.js to Supabase
Now, let's connect our Next.js application to our Supabase project. We'll need your project's API URL and anon key, which you can find in the Supabase project dashboard under Settings > API. Copy these values; we'll use them in our Next.js application. We need to create a Supabase client instance. Create a file called lib/supabase.js (or similar) in your Next.js project and paste the following code:
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
This code initializes the Supabase client with your project's URL and anonymous key. We're also using environment variables to securely store your API keys. Make sure you set the NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables in your .env.local file in the root of your Next.js project. You can copy those values from your Supabase dashboard. Using .env.local is a safe way to store the API keys because it keeps them out of your source code repository. Make sure that you don't commit your .env.local file to the repository. This is an important step for security and to keep your keys safe. This configuration is the gateway between your Next.js app and the Supabase backend.
Building the Chat UI with Next.js
Now, let's build the user interface for our chat app. In your pages/index.js file (or the main page of your application), we'll create the basic layout for our chat interface. This will include an input field for typing messages, a button to send messages, and a section to display the chat messages. You can use HTML, CSS, and React components to build the UI as needed. For styling, you can use any CSS framework like Tailwind CSS, Bootstrap, or plain CSS. For the example, we will keep it simple. Here's a basic example:
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
export default function Home() {
const [messages, setMessages] = useState([])
const [newMessage, setNewMessage] = useState('')
useEffect(() => {
// Fetch initial messages
}, [])
const handleSendMessage = async () => {
// Send message
}
return (
<div>
<h1>Real-Time Chat</h1>
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.username}: </strong>
{message.content}
</div>
))}
</div>
<div>
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
/>
<button onClick={handleSendMessage}>Send</button>
</div>
</div>
)
}
This is a simple structure; it renders an input field, a send button, and displays messages. We will add the logic to fetch messages from Supabase, send new messages, and, most importantly, listen for real-time updates. The core part of the UI is the messages.map function, which iterates through the messages array and renders each message. With this structure set, we can now add the real-time functionality.
Implementing Real-Time Chat
This is where the magic happens! We'll use Supabase Realtime to listen for new messages and update the chat UI in real-time. First, let's fetch the initial messages from Supabase when the component mounts. Update the useEffect hook in your pages/index.js to fetch messages and subscribe to real-time events:
useEffect(() => {
const fetchMessages = async () => {
const { data, error } = await supabase
.from('messages')
.select('*')
.order('created_at', { ascending: true })
if (error) {
console.error('Error fetching messages:', error)
} else {
setMessages(data)
}
}
fetchMessages()
const subscription = supabase
.from('messages')
.on('INSERT', (payload) => {
setMessages((prevMessages) => [...prevMessages, payload.new])
})
.subscribe()
return () => {
subscription.unsubscribe()
}
}, [])
In this code, we first fetch the existing messages from the messages table and order them by the created_at timestamp. Then, we use the .on('INSERT', ...) method to subscribe to the INSERT events on the messages table. Every time a new message is inserted into the table, this subscription will trigger, and we update our state by appending the new message to the existing messages. We're using the payload.new object, which contains the data of the new message. The subscribe() method starts listening for real-time changes. The unsubscribe() function cleans up the subscription when the component unmounts. This prevents memory leaks and ensures our app doesn't keep listening for updates when it's not needed.
Next, let's implement the handleSendMessage function to send new messages. Add this function to your pages/index.js:
const handleSendMessage = async () => {
const { data: { user }, error } = await supabase.auth.getUser()
if (error) {
console.error('Error getting user:', error)
return
}
if (!newMessage.trim()) return
const { error: insertError } = await supabase
.from('messages')
.insert({
user_id: user.id,
username: user.email.split('@')[0],
content: newMessage,
})
if (insertError) {
console.error('Error inserting message:', insertError)
} else {
setNewMessage('')
}
}
This function first retrieves the currently authenticated user using supabase.auth.getUser(). This ensures that we know who's sending the message. After verifying the user and checking the message is not empty, it inserts the message into the messages table, including the user's ID, a username (extracted from the user's email), and the message content. When the message has been sent, it clears the input field. The error handling is essential, and it prevents the app from crashing. With these functions, the messages will be displayed in real-time.
Adding Authentication
For a more complete chat application, you'll likely want to add authentication. Supabase makes this super easy! You can use Supabase Auth to handle user registration, login, and sessions. First, enable Email/Password authentication in your Supabase project under Auth > Settings. Next, install the necessary dependencies: npm install @supabase/auth-ui @supabase/auth-ui-shared or yarn add @supabase/auth-ui @supabase/auth-ui-shared. This will help us build a user interface. Now, we can incorporate these into the application. We can create a simple login and register page and protect the chat page from unauthenticated users.
import { Auth } from '@supabase/auth-ui'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
export default function AuthPage() {
const session = useSession()
const supabase = useSupabaseClient()
if (session) {
return <div>You are signed in! <button onClick={() => supabase.auth.signOut()}>Sign out</button></div>
}
return (
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
/>
)
}
This code utilizes the Supabase Auth UI components to handle authentication. This means we can get the login and registration UI for the users. This component uses the useSession hook to check the user's current session, useSupabaseClient hook to get the Supabase client instance, and renders the Supabase Auth UI if the user is not signed in. The appearance prop allows you to customize the appearance of the Auth UI using the ThemeSupa theme. You can also implement a sign-out function.
To manage protected routes, we will use the useSession hook from @supabase/auth-helpers-react to check if a user is logged in. If the user is authenticated, we will render the chat component; otherwise, we redirect them to the login page. This guarantees that only authenticated users can access the chat.
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
import Chat from './Chat'
export default function Home() {
const session = useSession()
const supabase = useSupabaseClient()
if (!session) {
return <AuthPage/>
}
return (
<Chat/>
)
}
This implementation provides a basic but effective way to handle user authentication in your application. By integrating authentication, you're enhancing your application's security and user experience, making your chat application much more useful.
Enhancements and Next Steps
Congratulations! You've successfully built a Supabase Realtime Chat app with Next.js! You should now have a fully functional real-time chat app that updates instantly when users send new messages. But we're not stopping here, are we? This is just the beginning. There's a ton of room for improvement and cool features you can add to take your chat app to the next level.
Here are some ideas to consider:
- User Profiles: Implement user profiles so users can have avatars, usernames, and other profile information.
- Private Messages: Allow users to send direct messages to each other.
- Message Editing and Deletion: Give users the ability to edit or delete their messages.
- Online Status: Show users' online/offline status in the chat.
- Notifications: Implement push notifications for new messages.
- File Uploads: Allow users to send files and images in the chat.
- UI/UX Improvements: Improve the design, add animations, and make the app more user-friendly.
- Testing: Write tests to ensure your app is working correctly and to prevent regressions.
Each of these improvements can significantly boost the functionality and appeal of your chat application. Building the features can be a fun project that can help you learn more about Next.js and Supabase. Keep experimenting, keep coding, and most importantly, keep learning. Remember, the best way to learn is by doing. So dive in and start building! Have fun, and happy coding, everyone!