Building a Chat Application using Upstash Kafka, Redis and Next.js
Project Description
In this blog post, we will be creating a messaging application that allows users to create message clients and chat room. Additionally, users will be able to access past messages.
The project consists of two pages. The first page is dedicated to client registration, where you can create multiple clients with unique names.
When you click on a client's username, you will be directed to the chatroom client associated with that specific user.
The logic of the chat application is as follows:
Users can create multiple clients on the index page, each with a unique username. Clicking on a client's username will open a new tab with a separate client with a unique path.
Each client will be connected to a message server via a WebSocket connection. When a new message is created on the client, it will be sent to the message server associated with that client.
The message servers will handle the message traffic. When a client sends a message through the WebSocket connection, the server will direct it to a Kafka Broker. Each message server will run a NodeJS thread to handle incoming messages. When a message is consumed, it will be sent to the clients through the existing WebSocket connection. To consume incoming messages on the client-side, we'll be using the react-use-websocket
library.
The application will utilize Upstash Redis to store message history. When a message is produced to Kafka, it will also be persisted to the Redis database. Upon creating a new client, the old messages will be retrieved from Upstash Redis and rendered in the chat display.
Here's the general overview of the application:
Note: In our implementation, we'll create single message server for demo purposes, one can increase the number of servers to handle the message load.
Demo
You can reach the demo of the app here. Current version of the application is deployed to Fly.
Getting Started
Here are the steps for building the chat application:
- Creating Upstash Redis database
- Creating Upstash Kafka cluster
- Creating the Next application (frontend).
- Creating the WebSocket message server.
- Deploying the application to Fly.io
Creating Upstash Redis database
Navigate to the Upstash Console and login, then on the Redis tab, click Create Database button.
Just like that, our Redis is ready to use! We'll come back to Redis console for the credentials.
Creating Upstash Kafka cluster
Now, switch to the Kafka Tab, and click Create Cluster button. Type in the cluster name and proceed. Then, create the Kafka topic and confirm.
Creating the Next App
First, create and navigate to the root folder of your application from your terminal. We'll keep the Next app and the server in this folder.
Then, create your next app.
Handling Credentials
We'll create a file named .env
to store the credentials. We won't need to copy and paste the credentials over and over again, we'll just import from this file.
First, create the .env
file.
Then, navigate to Redis console, and copy/paste the UPSTASH_REDIS_REST_URL
and UPSTASH_REDIS_REST_TOKEN
credentials to .env
file.
Finally, switch to Kafka console, and transfer the UPSTASH_KAFKA_REST_URL
, UPSTASH_KAFKA_REST_USERNAME
, UPSTASH_KAFKA_REST_PASSWORD
Now, your .env
file should look similar
Now that we've configured the credentials, we may proceed with the application.
Client Registration Page
Index page will contain the client registration/creation operations. When a username is submitted, a new client will be created and listed under the Current Clients table.
If you want to reset the chat history each time the app is reloaded, you can use the following function:
With that, index page is ready to run. Run npm run dev
command in next-chat-app
folder to see the index page go live.
Message Client Page
To implement dynamic routing for the clients, we will create a folder named /pages/user/[username].tsx
This folder structure will allow us to create dynamic routes for each individual client based on their username.
Here's the main component of the client. This component will hold the states for the list of messages, username etc. We are using useWebSocket hook to create message, connection and disconnection events from the WebSocket. When a message event is emitted, message will be added to message list and MessageDisplay component will be rerendered.
Here are the MessageDisplay and MessageInput components:
In order to provide the chat history to the client, we'll use getServerSideProps()
function.
Our Next.js app is now working. Refresh the page, create the clients and navigate one of them. You'll see the client page. But still, we need the message servers to handle the message flow.
Creating Message Server
Server's structure is rather simple. We're going to use Node.js, ws library, and Upstash Kafka to make it work. First, create a server
folder inside the chat-app folder
.
Inside the server
folder, we'll install the requirements and configure the files.
Then, we're going to create the WebSocket, Kafka Producer and Kafka Consumer clients inside /server/message_server.ts
file:
In order to interact with the WebSocket, we're creating connection
and message
events.
Finally, we'll create and run the thread that consumes messages with predefined interval:
Everything is ready. Our app should be working like a charm right now. If you run the message server on local and refresh the client page, you can see the messages transmitted between clients. The commands below will transcompile the TS file and run the server on localhost:8000
Deployment
We'll use Fly.io for the deployment. Please create an account before we start, if you don't have it already.
Deploying the Message Server
Go to the server
folder and install the flyctl
CLI tool, and authorize via shell
To create the configuration files, run flyctl init
. This will create a fly.toml
. Go to fly.toml
and insert the following lines for the WebSocket connection configurations:
Now, a final step for the servers. Run flyctl deploy
, and we're ready to go! When the deployment process is completed, flyctl will provide an endpoint for your server. Please copy that endpoint. In our case, the endpoint is message-server.fly.dev
.
Deploying the Next Application
Before deploying the Next.js application, we need to embed the deployment endpoint of the message server. Please replace the WebSocket URL in the pages/user/[username].tsx
file from ws://localhost:8080
to the endpoint from flyctl
, combined with wss://
prefix. In our case, it is wss://message-server.fly.dev
.
Then, in the next-chat-app
folder, run the identical commands as server
. This time, we don't need to edit the fly.toml
file, so we can proceed without that step.
flyctl init
flyctl deploy
We are done! If you run the flyctl open
command, you'll be navigated to your deployed project.
Conclusion and Suggestions
Thank you for following along!
You can find the Github repository of the project here.
If you want to keep working on the project, here are some suggestions:
-
Currently, whenever the page is reloaded, all the stored messages in Upstash Redis are being flushed. This behavior is controlled by the code in the
pages/index.tsx
file, specifically within thegetServerSideProps
function. However, a critical issue arises when a user decides to reload the page, it results in the deletion of chat history for all participants in the chatroom.
To solve this issue, a recommended solution involves implementing an extension of the TTL for the chat history each time a message is sent. This improvement would ensure that chat history remains accessible and preserved even after page reloads. -
You could implement multiple chatrooms feature. To achieve this, you could create multiple Kafka topics with unique names for each chatroom. Another way is to handle it on the message server itself using the right data structures.
-
You could also implement multiple message servers, and a load balancer to apply the best system design practices.
If you have any questions, you can reach me at fahreddin@upstash.com