Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
07 Sep 2025 · 5 min read ·Article 91 / 110
Go

91. Case Study: A gRPC-Based Todo List Application

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

91. Case Study: A gRPC-Based Todo List Application

gRPC has become one of the most popular technologies for building high-performance and efficient microservices. In this article, I’ll walk through a simple case study: building a Todo List application using gRPC from scratch. We’ll explore the architecture, the protocol structure, and Go code snippets that you can directly adapt for real-world scenarios.


Why gRPC?

Before diving into the case study, let’s take a moment to discuss why we might choose gRPC:

  • Efficiency: gRPC uses Protobuf, which is lighter and faster than traditional JSON/HTTP.
  • Interoperability: It supports many languages: Go, Java, C#, Python, and so on.
  • Contract-first API: With the .proto file, the contract between client and server is explicit and strongly-typed.
  • Streaming: It supports bidirectional data streaming, making it a great fit for real-time use cases.

Todo List Application Architecture

We’ll build the Todo List application with the following architecture:

  • Client: A CLI or frontend that sends requests to the gRPC server.
  • Server: Handles requests and stores tasks in simple in-memory storage.

Flow Diagram

Let’s illustrate the process of adding and retrieving todos using a mermaid flowchart:

MERMAID
flowchart TD
    Client-->|AddTodo|gRPC_Server
    gRPC_Server-->|Save data|Storage[In-memory]
    Client-->|GetTodos|gRPC_Server
    gRPC_Server-->|Fetch todos|Storage
    Storage-->|Todos list|gRPC_Server
    gRPC_Server-->|Todos response|Client

1. Defining the Protobuf

The .proto file describes the data structures and the service.

protobuf
 1// todo.proto
 2syntax = "proto3";
 3
 4package todo;
 5
 6// Message Type
 7message TodoItem {
 8  int32 id = 1;
 9  string task = 2;
10  bool completed = 3;
11}
12
13message AddTodoRequest {
14  string task = 1;
15}
16
17message AddTodoResponse {
18  TodoItem item = 1;
19}
20
21message GetTodosRequest {}
22
23message GetTodosResponse {
24  repeated TodoItem items = 1;
25}
26
27// Service
28service TodoService {
29  rpc AddTodo (AddTodoRequest) returns (AddTodoResponse);
30  rpc GetTodos (GetTodosRequest) returns (GetTodosResponse);
31}

2. Generate Code from the Proto

Use the Protobuf plugin for Go:

bash
1protoc --go_out=. --go-grpc_out=. todo.proto

3. Implementing the gRPC Server in Golang

Create a server.go file:

go
 1package main
 2
 3import (
 4    "context"
 5    "log"
 6    "net"
 7    "sync"
 8
 9    pb "path/to/generated/todo"
10    "google.golang.org/grpc"
11)
12
13type server struct {
14    pb.UnimplementedTodoServiceServer
15    todos []pb.TodoItem
16    mu    sync.Mutex
17    nextID int32
18}
19
20func (s *server) AddTodo(ctx context.Context, req *pb.AddTodoRequest) (*pb.AddTodoResponse, error) {
21    s.mu.Lock()
22    defer s.mu.Unlock()
23    s.nextID++
24    item := pb.TodoItem{
25        Id:        s.nextID,
26        Task:      req.GetTask(),
27        Completed: false,
28    }
29    s.todos = append(s.todos, item)
30    return &pb.AddTodoResponse{Item: &item}, nil
31}
32
33func (s *server) GetTodos(ctx context.Context, req *pb.GetTodosRequest) (*pb.GetTodosResponse, error) {
34    s.mu.Lock()
35    defer s.mu.Unlock()
36    // Copy the slice for concurrent safety
37    items := make([]*pb.TodoItem, len(s.todos))
38    for i, todo := range s.todos {
39        copied := todo
40        items[i] = &copied
41    }
42    return &pb.GetTodosResponse{Items: items}, nil
43}
44
45func main() {
46    lis, err := net.Listen("tcp", ":50051")
47    if err != nil {
48        log.Fatalf("failed to listen: %v", err)
49    }
50    s := grpc.NewServer()
51    pb.RegisterTodoServiceServer(s, &server{})
52    log.Println("gRPC server running at :50051")
53    if err := s.Serve(lis); err != nil {
54        log.Fatalf("failed to serve: %v", err)
55    }
56}

4. Implementing a Simple Client

Create a client.go file:

go
 1package main
 2
 3import (
 4    "context"
 5    "log"
 6    "time"
 7
 8    pb "path/to/generated/todo"
 9    "google.golang.org/grpc"
10)
11
12func main() {
13    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
14    if err != nil {
15        log.Fatalf("did not connect: %v", err)
16    }
17    defer conn.Close()
18    c := pb.NewTodoServiceClient(conn)
19
20    // Simulate AddTodo
21    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
22    defer cancel()
23    r, err := c.AddTodo(ctx, &pb.AddTodoRequest{Task: "Buy coffee"})
24    if err != nil {
25        log.Fatalf("could not add: %v", err)
26    }
27    log.Printf("Added Todo: %v", r.GetItem())
28
29    // Simulate GetTodos
30    resp, err := c.GetTodos(ctx, &pb.GetTodosRequest{})
31    if err != nil {
32        log.Fatalf("could not get todos: %v", err)
33    }
34    for _, t := range resp.GetItems() {
35        log.Printf("%d: %s [completed: %v]", t.GetId(), t.GetTask(), t.GetCompleted())
36    }
37}

5. Simulating Client-Server Interaction

Let’s imagine a simulation of adding two todos and then fetching the todo list:

1. Add “Buy coffee”
Client → Server: AddTodo("Buy coffee")
Server adds: {id: 1, task: 'Buy coffee', completed: false}

2. Add “Morning walk”
Client → Server: AddTodo("Morning walk")
Server adds: {id: 2, task: 'Morning walk', completed: false}

3. GetTodos
Client → Server: GetTodos()
Server response:

idtaskcompleted
1Buy coffeefalse
2Morning walkfalse

6. Strengths and Limitations

Let’s compare a gRPC-based Todo application against a simple REST one in a table:

FeatureREST APIgRPC
SerializationJSONProtobuf (binary)
SpeedMediumVery Fast
Data TypingWeakly-typed (runtime)Strongly-typed (compile)
StreamingNot a good fitNative bidi support
Contract DocumentationSwagger/OpenAPI.proto (more consistent)
InteroperabilityHighHigh
Browser SupportNative (can fetch)No, requires a gateway

Drawbacks of gRPC

  • A browser client must go through a proxy/gateway.
  • The initial communication setup is a bit more complex.
  • For small/monolithic applications, REST may be “enough.”

7. Conclusion

gRPC is the right choice for systems that need performance, strong data typing, or inter-service communication under heavy load. This Todo List case study demonstrates just how economical, expressive, and scalable gRPC can be.

If you want to explore further, try adding streaming to the status-update feature (CompleteTodo over a stream), or integrate a real database such as PostgreSQL.


Further Learning Resources


Let’s get started refactoring your REST API to gRPC for a more optimal and scalable experience! 🚀

Related Articles

💬 Comments