Building a Real-Time Chat Application with gRPC and Go

Atharv Bhadange
5 min readNov 3, 2023

--

gRPC, an open-source Remote Procedure Call framework, offers efficient communication between client and server applications. To make gRPC work, you need a well-defined message structure, which is defined in a Protocol Buffers (protobuf) file. In this article, we’ll show you how to set up a real-time chat application using gRPC in Go, including protobuf message definition and server-side implementation.

Prerequisites: Before diving into the tutorial, make sure you have a basic understanding of Go programming and goroutines. Familiarity with protobuf will also be beneficial.

Setting Up the Project:

  1. Start by creating a proto directory within your project's root directory to store your protobuf file.
  2. Define your message structures in the protobuf file. While for this blog, we keep all the messages in one file, it’s good practice to structure your files logically.
syntax = "proto3";

package chat;

option go_package = "/gen";

import "google/protobuf/timestamp.proto";

message User {
string id = 1;
string name = 2;
}

message Message {
string id = 1;
string content = 2;
google.protobuf.Timestamp timestamp = 3;
}

message Connect {
User user = 1;
bool active = 2;
}

message Close {}

service Broadcast {
rpc CreateStream(Connect) returns (stream Message);
rpc BroadcastMessage(Message) returns (Close);
}

Generating Go Code: To generate Go code from the protobuf structure, you need to install the Protocol Buffers compiler and its Go plugin. You can follow the installation guide on the gRPC official website (https://grpc.io/docs/languages/go/quickstart/).

After installation, use the following command to generate the Go code:

protoc --go_out=. --go-grpc_out=. proto/chat.proto

This command creates a gen directory with two autogenerated files with .pb.go extensions. Remember not to edit these files.

Handling Import Errors: If you encounter import errors in the generated files, resolve them by running the following commands:

go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
go get -u google.golang.org/grpc

Now, you’re ready to start coding the server side of your gRPC chat application.

Server Implementation: First, create handler functions and services as defined in the protobuf file. To handle connections and maintain a pool of connections, you’ll need to define some Go structs.

type Connection struct {
proto.UnimplementedBroadcastServer
stream proto.Broadcast_CreateStreamServer
id string
active bool
error chan error
}

type Pool struct {
proto.UnimplementedBroadcastServer
Connection []*Connection
}

Here, the proto directory refers to the /gen directory we previously created.

To implement the BroadcastServer interface, embed the UnimplementedBroadcastServer struct in your struct. The CreateStream function connects and listens to messages published in a group.

func (p *Pool) CreateStream(pconn *proto.Connect, stream proto.Broadcast_CreateStreamServer) error {
conn := &Connection{
stream: stream,
id: pconn.User.Id,
active: true,
error: make(chan error),
}

p.Connection = append(p.Connection, conn)

return <-conn.error
}

The BroadcastMessage function iterates through all active connections and sends messages asynchronously using goroutines to avoid blocking operations.

func (s *Pool) BroadcastMessage(ctx context.Context, msg *proto.Message) (*proto.Close, error) {
wait := sync.WaitGroup{}
done := make(chan int)

for _, conn := range s.Connection {
wait.Add(1)

go func(msg *proto.Message, conn *Connection) {
defer wait.Done()

if conn.active {
err := conn.stream.Send(msg)
fmt.Printf("Sending message to: %v from %v", conn.id, msg.Id)

if err != nil {
fmt.Printf("Error with Stream: %v - Error: %v\n", conn.stream, err)
conn.active = false
conn.error <- err
}
}
}(msg, conn)

}

go func() {
wait.Wait()
close(done)
}()

<-done
return &proto.Close{}, nil
}

The sync.WaitGroup is used to ensure the connection to the stream remains open until messages are sent to all active connections.

Creating a gRPC Server: In your main.go file, create the gRPC server for your chat application.

func main() {
// Create a new gRPC server
grpcServer := grpc.NewServer()

// Create a new connection pool
var conn []*handler.Connection

pool := &handler.Pool{
Connection: conn,
}

// Register the pool with the gRPC server
proto.RegisterBroadcastServer(grpcServer, pool)

// Create a TCP listener at port 8080
listener, err := net.Listen("tcp", ":8080")

if err != nil {
log.Fatalf("Error creating the server %v", err)
}

fmt.Println("Server started at port :8080")

// Start serving requests at port 8080
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Error creating the server %v", err)
}
}

Run your application with the following command:

go build -o main && main.go

Testing with Postman: To test your real-time chat application, you can use Postman:

  1. Create a new gRPC request

2. Enter the URL localhost:8080 and import the protobuf file from your project.

3. You will automatically get the two methods created in the functions.

4. First, create a connection to the stream and invoke the method.

5. In another tab, create a broadcaster.

6. When you invoke the method, you will see the received message in the first tab.

7. Similarly, by connecting from multiple tabs, the list will grow.

In this article, we’ve demonstrated how to build a real-time chat application using gRPC and Go. By following the steps for protobuf message definition, server-side implementation, and testing with Postman, you can create your chat application with ease.

Do leave your feedback, I am willing to know what works and what does not.

Find the full source code below:

https://github.com/atharv-bhadange/grpc-chat

--

--