Build a Chat Room Application with Redpanda and Golang
Create a basic chat room application with Redpanda and Kafka clients developed with franz-go.
This tutorial describes how to:
- Start a Redpanda cluster to store and stream chat room messages.
- Write a client application in Typescript to produce and consume chat room messages.
- Build and run multiple clients to exchange chat messages streamed through Redpanda.
Prerequisites
Download and install Go from go.dev.
Set up a Redpanda environment
Complete the Redpanda Quickstart before continuing. This tutorial expands on the quickstart. You can choose to run either one or three brokers.
If you're running Redpanda on your laptop, or in a shared development
environment, then avoid Redpanda's optimized production settings. Avoid running
sudo rpk redpanda tune all
or manually configuring Redpanda for production, as they
might affect your experience with other applications running on your machine.
Set up your environment
Create and set your working directory to the project folder,
chat-room
:mkdir chat-room
cd chat-roomInitialize the project:
go mod init com/redpanda/chat-room
Install the required dependencies:
go get github.com/twmb/franz-go
go get github.com/twmb/franz-go/pkg/kadm
go get github.com/google/uuid
Create a topic
You need a topic named chat-room
for both Redpanda and the client to use to store chat room messages. If you completed the Redpanda Quickstart, this topic already exists in your cluster.
Verify that the
chat-room
topic exists in your cluster by listing all topics:docker exec -it redpanda-0 rpk topic list
Output:
NAME PARTITIONS REPLICAS
chat-room 1 1If the topic doesn't exist yet, use rpk to create a
chat-room
topic:docker exec -it redpanda-0 rpk topic create chat-room
Output:
TOPIC STATUS
chat-room OK
Confirm that the topic exists on the client side
The client may not always know that the topic exists. You can verify that it exists and create it if not. In chat-room/
create a source file called admin.go
with the following content.
package main
import (
"context"
"fmt"
"github.com/twmb/franz-go/pkg/kadm"
"github.com/twmb/franz-go/pkg/kgo"
)
type Admin struct {
client *kadm.Client
}
func NewAdmin(brokers []string) *Admin {
client, err := kgo.NewClient(
kgo.SeedBrokers(brokers...),
)
if err != nil {
panic(err)
}
admin := kadm.NewClient(client)
return &Admin{client: admin}
}
func (a *Admin) TopicExists(topic string) bool {
ctx := context.Background()
topicsMetadata, err := a.client.ListTopics(ctx)
if err != nil {
panic(err)
}
for _, metadata := range topicsMetadata {
if metadata.Topic == topic {
return true
}
}
return false
}
func (a *Admin) CreateTopic(topic string) {
ctx := context.Background()
resp, err := a.client.CreateTopics(ctx, 1, 1, nil, topic)
if err != nil {
panic(err)
}
for _, ctr := range resp {
if ctr.Err != nil {
fmt.Printf("Unable to create topic '%s': %s", ctr.Topic, ctr.Err)
} else {
fmt.Printf("Created topic '%s'\n", ctr.Topic)
}
}
}
func (a *Admin) Close() {
a.client.Close()
}
Create a producer
A client needs a producer to publish chat-room
topic messages.
To create a producer for the client, in chat-room/
create a source file producer.go
with the following content.
package main
import (
"context"
"encoding/json"
"github.com/twmb/franz-go/pkg/kgo"
)
type Producer struct {
client *kgo.Client
topic string
}
func NewProducer(brokers []string, topic string) *Producer {
client, err := kgo.NewClient(
kgo.SeedBrokers(brokers...),
)
if err != nil {
panic(err)
}
return &Producer{client: client, topic: topic}
}
func (p *Producer) SendMessage(user, message string) {
ctx := context.Background()
msg := Message{User: user, Message: message}
b, _ := json.Marshal(msg)
p.client.Produce(ctx, &kgo.Record{Topic: p.topic, Value: b}, nil)
}
func (p *Producer) Close() {
p.client.Close()
}
You now have a working producer that sends strings entered by the user to the
chat-room
topic. Messages are sent as JSON encoded strings here,
but keep in mind that the producer only sends buffers, so you can encode the
messages however you like.
Create a consumer
A client needs a consumer to receive chat-room
topic messages.
To create a consumer for the client, in chat-room/
create a source file consumer.go
with the following content.
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/twmb/franz-go/pkg/kgo"
"github.com/google/uuid"
)
type Consumer struct {
client *kgo.Client
topic string
}
func NewConsumer(brokers []string, topic string) *Consumer {
groupID := uuid.New().String()
client, err := kgo.NewClient(
kgo.SeedBrokers(brokers...),
kgo.ConsumerGroup(groupID),
kgo.ConsumeTopics(topic),
kgo.ConsumeResetOffset(kgo.NewOffset().AtStart()),
)
if err != nil {
panic(err)
}
return &Consumer{client: client, topic: topic}
}
func (c *Consumer) PrintMessages() {
ctx := context.Background()
for {
fetches := c.client.PollFetches(ctx)
iter := fetches.RecordIter()
for !iter.Done() {
record := iter.Next()
var msg Message
if err := json.Unmarshal(record.Value, &msg); err != nil {
fmt.Printf("Error decoding message: %v\n", err)
continue
}
fmt.Printf("%s: %s\n", msg.User, msg.Message)
}
}
}
func (c *Consumer) Close() {
c.client.Close()
}
You now have a consumer that reads all messages from the chat-room
topic and prints them to the console. You can start as many consumer groups as
you like, but remember that each group reads a message only once, which is
why the example is using a generated UUID for the group ID. This way, each time you run the application, you see all previous messages.
Create a client application
The client needs an application that creates the topic, producer, and consumer and implements the chat logic.
To create a client application, in chat-room/
create a source file main.go
with the following content.
The broker settings in this code are from the Redpanda Quickstart, where the external port for broker redpanda-0
is set to port 19092. If you're not using the Redpanda Quickstart, make sure that the broker's port is correct for your deployment.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
type Message struct {
User string `json:"user"`
Message string `json:"message"`
}
func main() {
topic := "chat-room"
brokers := []string{"localhost:19092"}
admin := NewAdmin(brokers)
defer admin.Close()
if !admin.TopicExists(topic) {
admin.CreateTopic(topic)
}
username := ""
fmt.Print("Enter your username: ")
fmt.Scanln(&username)
producer := NewProducer(brokers, topic)
defer producer.Close()
consumer := NewConsumer(brokers, topic)
defer consumer.Close()
go consumer.PrintMessages()
fmt.Println("Connected. Press Ctrl+C to exit")
reader := bufio.NewReader(os.Stdin)
for {
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
producer.SendMessage(username, message)
}
}
Build and run the application
Build the client chat application, run it from multiple client terminals, and chat between the clients.
Open at least two terminals, and for each terminal:
Run the client application:
go run .
When prompted with
Enter user name:
, enter a unique name for the chat room.Use the chat application: enter a message in a terminal, and verify that the message is received in the other terminals.
For example:
Enter user name:
Alice
Connected, press Ctrl+C to exit
Alice: Hi, I'm Alice
Bob: Hi Alice, I'm Bob, nice to meet you
Next steps
This is a basic example of a chat room application. You can improve this application by implementing additional features and components, such as:
- A user interface to make it more interactive and user-friendly.
- A user registration and login system to authenticate users before they can access the chat room.
- Rate limiting and other measures to prevent spamming and abuse in the chat room.
For additional resources to help you build stream processing applications that can aggregate, join, and filter your data streams, see: