Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
06 Jul 2025 · 4 min read ·Article 28 / 110
Go

28 Case Study: A Simple Streaming Chat

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

Chat has become a must-have feature in many modern applications today. Whether it’s an online store website, an educational app, or a community platform—they all need real-time interaction to make users feel more engaged. In this article, I’ll walk through a simple case study: how to design and implement a streaming chat system using gRPC Streaming, a technology that has become popular in modern backend environments.

Case Study: Requirements & Architecture

Feature Requirements

  1. Users can send chat messages.
  2. All connected users can receive the latest chat messages in real time.
  3. Every user can see the chat session history when they join.
  4. The system is simple enough to run locally.
  5. Basic tolerance for multiple clients.

Architecture Diagram

MERMAID
flowchart TD
    Client1 -->|Bidirectional Streaming| gRPCServer
    Client2 -->|Bidirectional Streaming| gRPCServer
    gRPCServer -->|Store Chat| DB[(Database)]
  • The client opens a bidirectional streaming connection with the gRPC server.
  • When a message is sent, the server stores it in a database (or in memory) and then broadcasts it to all active clients.

1. Preparation: Technology Stack

  • Language: Go
  • gRPC Framework: google.golang.org/grpc
  • Protobuf Compiler: protoc
  • Database: In-memory (slice/array) or SQLite/Redis (optional for persistence)

2. Protobuf Definition

chat.proto

proto
 1syntax = "proto3";
 2
 3package chat;
 4
 5message ChatMessage {
 6  string username = 1;
 7  string message = 2;
 8  string timestamp = 3;
 9}
10
11service ChatService {
12  rpc ChatStream(stream ChatMessage) returns (stream ChatMessage);
13}

3. Implementing the gRPC Server in Go

go
 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"log"
 7	"net"
 8	"sync"
 9	"time"
10
11	pb "yourmodule/chat"
12	"google.golang.org/grpc"
13)
14
15type chatServer struct {
16	pb.UnimplementedChatServiceServer
17	mu      sync.Mutex
18	clients map[string]pb.ChatService_ChatStreamServer
19	msgs    []pb.ChatMessage
20}
21
22func (s *chatServer) ChatStream(stream pb.ChatService_ChatStreamServer) error {
23	id := fmt.Sprintf("client-%d", time.Now().UnixNano())
24	s.mu.Lock()
25	s.clients[id] = stream
26	for _, msg := range s.msgs {
27		stream.Send(&msg)
28	}
29	s.mu.Unlock()
30
31	defer func() {
32		s.mu.Lock()
33		delete(s.clients, id)
34		s.mu.Unlock()
35	}()
36
37	for {
38		msg, err := stream.Recv()
39		if err != nil {
40			log.Printf("client %s disconnected", id)
41			return err
42		}
43
44		msg.Timestamp = time.Now().Format(time.RFC3339)
45		s.mu.Lock()
46		s.msgs = append(s.msgs, *msg)
47		for _, c := range s.clients {
48			if err := c.Send(msg); err != nil {
49				log.Println("send error:", err)
50			}
51		}
52		s.mu.Unlock()
53	}
54}
55
56func main() {
57	lis, err := net.Listen("tcp", ":50051")
58	if err != nil {
59		log.Fatalf("failed to listen: %v", err)
60	}
61	server := grpc.NewServer()
62	pb.RegisterChatServiceServer(server, &chatServer{
63		clients: make(map[string]pb.ChatService_ChatStreamServer),
64	})
65	log.Println("gRPC chat server started on :50051")
66	server.Serve(lis)
67}

4. Simulating a gRPC Client

go
 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"log"
 7	"time"
 8
 9	pb "yourmodule/chat"
10	"google.golang.org/grpc"
11)
12
13func main() {
14	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
15	if err != nil {
16		log.Fatalf("fail connect: %v", err)
17	}
18	defer conn.Close()
19
20	client := pb.NewChatServiceClient(conn)
21	stream, err := client.ChatStream(context.Background())
22	if err != nil {
23		log.Fatalf("fail stream: %v", err)
24	}
25
26	// Receive stream
27	go func() {
28		for {
29			msg, err := stream.Recv()
30			if err != nil {
31				log.Fatal(err)
32			}
33			fmt.Printf("[%s] %s: %s\n", msg.Timestamp, msg.Username, msg.Message)
34		}
35	}()
36
37	// Send messages
38	for {
39		var text string
40		fmt.Print("> ")
41		fmt.Scanln(&text)
42		stream.Send(&pb.ChatMessage{
43			Username: "ihsan",
44			Message:  text,
45		})
46	}
47}

5. Table of Components and Their Functions

ComponentFunction
ChatService.ChatStreamThe main gRPC endpoint for bidirectional streaming chat
ChatMessageThe data structure for messages sent and received
map[string]streamThe list of active client connections for broadcasting
[]ChatMessageThe chat history resent when a client connects

6. Process Sequence Diagram

MERMAID
sequenceDiagram
  participant Client as Klien
  participant Server as gRPC Server

  Client->>Server: Buka Stream
  Server-->>Client: Kirim histori pesan
  loop Chat
    Client->>Server: Kirim ChatMessage
    Server->>Server: Simpan pesan
    Server->>Client: Broadcast ke semua klien
  end

7. Conclusion

By leveraging gRPC bidirectional streaming, we can build a real-time chat system that is lightweight, fast, and efficient for many clients. This approach fits a wide range of modern backend needs, from live support features to collaboration apps.

For further development, you can add authentication, persist messages to a real database, or integrate a pub/sub system like Redis so it can scale horizontally.

Happy coding, and I hope this is helpful! 🚀

Related Articles

💬 Comments