Getting started with gRPC in Golang
Tags: go, grpc, mongodb, backend
Published: 2023-05-23 12:44:33

Microservices architecture is one of the preferred methods of building scalable and robust applications. It involves breaking large applications into smaller components that are well-defined, performs a specific task and uses sets of application programming interface (API) for their communication.

Communication is an essential part of microservices; it plays an important role in letting services talk to each other within the larger application context. Some examples of protocol microservices use to communicate with each other includes HTTP, **gRPC**, message brokers, etc.

In this article, we will explore what gRPC is and how to get started by building a user management service using gRPC, MongoDB and Golang.

## What is gRPC?

gRPC is a modern communication framework that can run in any environment and helps connect services efficiently. It was introduced in 2015 and governed by the Cloud Native Computing Platform (CNCF). Beyond efficiently connecting services across a distributed system, mobile applications, frontend to backend, etc., it supports health checking, load balancing, tracing, and authentication.

gRPC offers a fresh perspective to developers building medium to complex applications as it can generate client and server bindings for multiple languages. The following are some of its benefits:

**Service definition**

gRPC uses Protocol Buffers as its interface description language, similar to JSON and provides features like authentication, cancellation, timeouts, etc.

**Lightweight and performant**

gRPC definitions are 30 percent smaller than JSON definitions and are 5 to 7 times faster than a traditional REST API.

**Multiple platform support**

gRPC is language agnostic and has automated code generation for client and server-supported languages.

**Scalable**

From the developer’s environment to production, gRPC is designed to scale millions ler seconds requests.

# Getting started

Now that we understand gRPC's importance in building scalable applications, let’s build a user management service with gRPC, MongoDB, and Golang. The project source code can be found [here](https://github.com/Mr-Malomz/grpc_go).

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

-   Basic understanding of Golang
-   Basic understanding of Protocol Buffer
-   Protocol Buffer [compiler](https://grpc.io/docs/protoc-installation/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) installed
-   A [MongoDB account](https://www.mongodb.com/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) to host the database. [**Signup**](https://www.mongodb.com/cloud/atlas/register?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) **is completely free**.
-   [Postman](https://www.postman.com/downloads/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) or any gRPC testing application

## Project and Dependencies setup

To get started, we need to navigate to the desired directory and run the command below in our terminal:

```bash
mkdir grpc_go && cd grpc_go
```

This command creates a Golang project called `grpc_go` and navigates into the project directory.

Next, we need to initialize a Go module to manage project dependencies by running the command below:

```bash
go mod init grpc_go
```

This command will create a `go.mod` file for tracking project dependencies.

We proceed to install the required dependencies with:

```bash
go get google.golang.org/grpc go.mongodb.org/mongo-driver/mongo github.com/joho/godotenv google.golang.org/protobuf
```

`google.golang.org/grpc` is the Golang implementation of gRPC.

`go.mongodb.org/mongo-driver/mongo` is a driver for connecting to MongoDB.

`github.com/joho/godotenv` is a library for managing environment variables.

`google.golang.org/protobuf` is the Golang implementation of Protocol Buffers.

## Defining the user management Protocol Buffer and compilation

To get started, we need to define a Protocol Buffer to represent all the operations and responses involved in the user management service. To do this, first, we need to create a `proto` folder in the root directory, and in this folder, create a `user.proto` file and add the snippet below:

```proto
syntax = "proto3";
package user;
option go_package = "grpc_go/proto";

service UserService {
    rpc GetUser (UserRequest) returns (UserResponse);
    rpc CreateUser (CreateUserRequest) returns (CreateUserResponse);
    rpc UpdateUser (UpdateUserRequest) returns (UpdateUserResponse);
    rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse);
    rpc GetAllUsers (Empty) returns (GetAllUsersResponse);
}

message UserRequest {
    string id = 1;
}

message UserResponse {
    string id = 1;
    string name = 2;
    string location = 3;
    string title = 4;
}

message CreateUserRequest {
    string name = 2;
    string location = 3;
    string title = 4;
}

message CreateUserResponse {
    string data = 1;
}

message UpdateUserRequest {
    string id = 1;
    string name = 2;
    string location = 3;
    string title = 4;
}

message UpdateUserResponse {
    string data = 1;
}

message DeleteUserRequest {
    string id = 1;
}

message DeleteUserResponse {
    string data = 1;
}

message Empty {}

message GetAllUsersResponse {
    repeated UserResponse users = 1;
}
```

The snippet above does the following:

-   Specifies the use of `proto3` syntax
-   Declares `user` as the package name
-   Uses the `go_package` option to define the import path of the package and where the generated code will be stored
-   Creates a `service` to Create, Read, Edit, and Delete (CRUD) a user and their corresponding responses as `message`s.

Lastly, we need to compile the `user.proto` file using the command below:

```bash
protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    proto/user.proto
```

The command above uses the Protocol Buffer [compiler](https://grpc.io/docs/protoc-installation/?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog) to generate Golang server and client code by specifying the relative part and using the `user.proto` file.

> To avoid errors, we must ensure we add Golang to the path.

On successful compilation, we should see `user_grpc.pb.go` and `user.pb.go` files added to the `proto` folder. These files contain the gRPC-generated server and client code. In this article, we will only use the server code.

![gRPC-generated files](https://paper-attachments.dropboxusercontent.com/s_342FCE23FB720A94B62FAADA71BB64C7EAFA3BCC57C036F2CB5FC2D41E7F294F_1683671528193_Screenshot+2023-05-07+at+23.04.33.png)

## Using the generated code from gRPC in our application

With the compilation process done, we can start using the generated code in our application.

**Database setup and integration**

First, we need to set up a database and a collection on MongoDB as shown below:

![database setup](https://paper-attachments.dropboxusercontent.com/s_342FCE23FB720A94B62FAADA71BB64C7EAFA3BCC57C036F2CB5FC2D41E7F294F_1683672826154_Screenshot+2023-05-09+at+23.49.07.png)

We also need to get our database connection string by clicking on the **Connect** button and changing the Driver to `Go`.

![](https://paper-attachments.dropboxusercontent.com/s_342FCE23FB720A94B62FAADA71BB64C7EAFA3BCC57C036F2CB5FC2D41E7F294F_1683672832009_Screenshot+2023-05-09+at+23.52.03.png)

Secondly, we must modify the copied connection string with the user's password we created earlier and change the database name. To do this, we need to create a `.env` file in the root directory and add the snippet copied:

```bash
MONGOURI=mongodb+srv://<YOUR USERNAME HERE>:<YOUR PASSWORD HERE>@cluster0.e5akf.mongodb.net/<DATABASE NAME>?retryWrites=true&w=majority
```

Sample of a properly filled connection string below:

```bash
MONGOURI=mongodb+srv://malomz:malomzPassword@cluster0.e5akf.mongodb.net/projectMngt?retryWrites=true&w=majority
```

Thirdly, we need to create a helper function to load the environment variable using the `github.com/joho/godotenv` library. To do this, we need to create a `configs` folder in the root directory; here, create an `env.go` file and add the snippet below:

```go
package configs

import (
    "log"
    "os"
    "github.com/joho/godotenv"
)

func EnvMongoURI() string {
    err := godotenv.Load()
    if err != nil {
        log.Fatal("Error loading .env file")
    }
    return os.Getenv("MONGOURI")
}
```

Fourthly, we need to create a model to represent our application data. To do this, we need to create `user_model.go` file in the same `configs` folder and add the snippet below:

```go
package configs

import "go.mongodb.org/mongo-driver/bson/primitive"

type User struct {
    Id       primitive.ObjectID `json:"id,omitempty"`
    Name     string             `json:"name,omitempty" validate:"required"`
    Location string             `json:"location,omitempty" validate:"required"`
    Title    string             `json:"title,omitempty" validate:"required"`
}
```

Lastly, we need to create a `db.go` file to implement our database logic in the same `configs` folder and add the snippet below:

```go
package configs

import (
    "context"
    "fmt"
    "log"
    "time"

    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

type dbHandler interface {
    GetUser(id string) (*User, error)
    CreateUser(user User) (*mongo.InsertOneResult, error)
    UpdateUser(id string, user User) (*mongo.UpdateResult, error)
    DeleteUser(id string) (*mongo.DeleteResult, error)
    GetAllUsers() ([]*User, error)
}

type DB struct {
    client *mongo.Client
}

func NewDBHandler() dbHandler {
    client, err := mongo.NewClient(options.Client().ApplyURI(EnvMongoURI()))
    if err != nil {
        log.Fatal(err)
    }

    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    err = client.Connect(ctx)
    if err != nil {
        log.Fatal(err)
    }

    //ping the database
    err = client.Ping(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected to MongoDB")

    return &DB{client: client}
}

func colHelper(db *DB) *mongo.Collection {
    return db.client.Database("projectMngt").Collection("User")
}

func (db *DB) CreateUser(user User) (*mongo.InsertOneResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    newUser := User{
        Id:       primitive.NewObjectID(),
        Name:     user.Name,
        Location: user.Location,
        Title:    user.Title,
    }
    res, err := col.InsertOne(ctx, newUser)

    if err != nil {
        return nil, err
    }

    return res, err
}

func (db *DB) GetUser(id string) (*User, error) {
    col := colHelper(db)
    var user User
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    err := col.FindOne(ctx, bson.M{"_id": objId}).Decode(&user)

    if err != nil {
        return nil, err
    }

    return &user, err
}

func (db *DB) UpdateUser(id string, user User) (*mongo.UpdateResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    update := bson.M{"name": user.Name, "location": user.Location, "title": user.Title}
    result, err := col.UpdateOne(ctx, bson.M{"_id": objId}, bson.M{"$set": update})

    if err != nil {
        return nil, err
    }

    return result, err
}

func (db *DB) DeleteUser(id string) (*mongo.DeleteResult, error) {
    col := colHelper(db)
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    objId, _ := primitive.ObjectIDFromHex(id)

    result, err := col.DeleteOne(ctx, bson.M{"_id": objId})

    if err != nil {
        return nil, err
    }

    return result, err
}

func (db *DB) GetAllUsers() ([]*User, error) {
    col := colHelper(db)
    var users []*User
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    results, err := col.Find(ctx, bson.M{})

    if err != nil {
        return nil, err
    }

    for results.Next(ctx) {
        var singleUser *User
        if err = results.Decode(&singleUser); err != nil {
            return nil, err
        }
        users = append(users, singleUser)
    }

    return users, err
}
```

The snippet above does the following:

-   Imports the required dependencies
-   **Line 15 - 21**: Defines a `dbHandler` interface that describes all the associated functions in our user management service
-   **Line 23 - 25**: Creates a `DB` struct with `col` property that will implement the `dbHandler` interface
-   **Line 27 - 48**: Creates a `NewDBHandler` constructor function that ties the `DB` struct and the `dbHandler` interface it implements by initializing the database connection to MongoDB and returning the appropriate response
-   **Line 50 - 52**: Creates a `colHelper` function that accepts the database connection by specifying the database name and associated collection
-   **Line 54 - 145**: Creates the required methods `CreateUser`, `GetUser`, `UpdateUser`, `DeleteUser`, and `GetAllUsers` with a `DB` pointer receiver and returns the appropriate responses. The methods also use the appropriate methods from MongoDB to perform the required operations

**Integrating the database logic with gRPC-generated code**

With our database logic setup, we can use the methods to create our application handlers. To do this, we need to create a `service` folder; here, create a `user_service.go` file and add the snippet below:

```go
package services

import (
    "context"
    "grpc_go/configs"
    pb "grpc_go/proto"
)

var db = configs.NewDBHandler()

type UserServiceServer struct {
    pb.UnimplementedUserServiceServer
}

func (service *UserServiceServer) GetUser(ctx context.Context, req *pb.UserRequest) (*pb.UserResponse, error) {
    resp, err := db.GetUser(req.Id)

    if err != nil {
        return nil, err
    }

    return &pb.UserResponse{Id: resp.Id.String(), Name: resp.Name, Location: resp.Location, Title: resp.Title}, nil
}

func (service *UserServiceServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
    newUser := configs.User{Name: req.Name, Location: req.Location, Title: req.Title}
    _, err := db.CreateUser(newUser)

    if err != nil {
        return nil, err
    }

    return &pb.CreateUserResponse{Data: "User created successfully!"}, nil
}

func (service *UserServiceServer) UpdateUser(ctx context.Context, req *pb.UpdateUserRequest) (*pb.UpdateUserResponse, error) {
    newUser := configs.User{Name: req.Name, Location: req.Location, Title: req.Title}
    _, err := db.UpdateUser(req.Id, newUser)

    if err != nil {
        return nil, err
    }

    return &pb.UpdateUserResponse{Data: "User updated successfully!"}, nil
}

func (service *UserServiceServer) DeleteUser(ctx context.Context, req *pb.DeleteUserRequest) (*pb.DeleteUserResponse, error) {
    _, err := db.DeleteUser(req.Id)

    if err != nil {
        return nil, err
    }

    return &pb.DeleteUserResponse{Data: "User details deleted successfully!"}, nil
}

func (service *UserServiceServer) GetAllUsers(context.Context, *pb.Empty) (*pb.GetAllUsersResponse, error) {
    resp, err := db.GetAllUsers()
    var users []*pb.UserResponse

    if err != nil {
        return nil, err
    }

    for _, v := range resp {
        var singleUser = &pb.UserResponse{
            Id:       v.Id.String(),
            Name:     v.Name,
            Location: v.Location,
            Title:    v.Title,
        }
        users = append(users, singleUser)
    }

    return &pb.GetAllUsersResponse{Users: users}, nil
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Initializes the database using the `NewDBHandler` constructor function
-   Creates a `UserServiceServer` that implements the gRPC-generated `UserServiceServer` interface inside the `user_grpc.pb.go` file
-   Creates the required methods by passing the `UserServiceServer` struct as a pointer and returning the appropriate responses as generated by gRPC

**Creating the server**

With that done, we can create the application gRPC server by creating a `main.go` file in the root directory and add the snippet below:

```go
package main

import (
    "log"
    "net"

    pb "grpc_go/proto"
    "grpc_go/services"
    "google.golang.org/grpc"
)
func main() {
    lis, err := net.Listen("tcp", "[::1]:8080")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    grpcServer := grpc.NewServer()
    service := &services.UserServiceServer{}

    pb.RegisterUserServiceServer(grpcServer, service)
    err = grpcServer.Serve(lis)

    if err != nil {
        log.Fatalf("Error strating server: %v", err)
    }
}
```

The snippet above does the following:

-   Imports the required dependencies
-   Specifies the application port using the in-built `net` package
-   Creates an instance of gRPC server using the `NewServer` method and specifies the associated service using the `UserServiceServer` struct
-   Register the service implementation with the gRPC server
-   Starts the server using the `Serve` method by passing the required port and handling errors appropriately

With that done, we can test our application by running the command below in our terminal.

```bash
go run main.go
```

## Testing with Postman

With our server up and running, we can test our application by creating a new **gRPC Request**.

![Create new](https://paper-attachments.dropboxusercontent.com/s_80F8DDD905BBAA506022F629DD8AAC7F4C11E43711F085CB17CB09D07EC7D70E_1683337484831_Screenshot+2023-04-28+at+00.18.35.png)

![Select gRPC Request](https://paper-attachments.dropboxusercontent.com/s_80F8DDD905BBAA506022F629DD8AAC7F4C11E43711F085CB17CB09D07EC7D70E_1683337484805_Screenshot+2023-04-28+at+00.19.06.png)

Input `grpc://[::1]:8080` as the URL, select the **Import a .proto file** option and upload the `user.proto` file we created earlier.

![URL and upload proto file](https://paper-attachments.dropboxusercontent.com/s_80F8DDD905BBAA506022F629DD8AAC7F4C11E43711F085CB17CB09D07EC7D70E_1683337591306_Screenshot+2023-04-28+at+00.27.32.png)

With that done, the corresponding method will be populated and we can test them accordingly.

![getAllUsers](https://paper-attachments.dropboxusercontent.com/s_80F8DDD905BBAA506022F629DD8AAC7F4C11E43711F085CB17CB09D07EC7D70E_1683337760054_Screenshot+2023-04-28+at+00.43.19.png)
![getAUser](https://paper-attachments.dropboxusercontent.com/s_80F8DDD905BBAA506022F629DD8AAC7F4C11E43711F085CB17CB09D07EC7D70E_1683337760024_Screenshot+2023-04-28+at+00.43.59.png)

We can also validate that our gRPC server works by checking our MongoDB collection

![Collection](https://paper-attachments.dropboxusercontent.com/s_342FCE23FB720A94B62FAADA71BB64C7EAFA3BCC57C036F2CB5FC2D41E7F294F_1683757798244_Screenshot+2023-05-10+at+23.29.30.png)

## Conclusion

This post discussed what gRPC is, its role in building scalable applications and how to get started by building a user management service with Golang and MongoDB. Beyond what was discussed above, gRPC offers robust techniques around authentication, error handling, performance, etc.

These resources might be helpful:

-   [Golang gRPC implementation](https://pkg.go.dev/google.golang.org/grpc?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [Golang Protocol Buffer implementation](https://pkg.go.dev/google.golang.org/protobuf?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)
-   [MongoDB Golang driver](https://github.com/mongodb/mongo-go-driver?utm_source=fullstackwriter&utm_medium=fullstackwriter-blog)