# Developing a REST API with Go and Cloud Run - GSP761

## **Overview**

For the labs in the [Serverless Cloud Run Development](https://www.cloudskillsboost.google/course_templates/741) course, you will read through a fictitious business scenario and assist the characters with their serverless migration plan.

Twelve years ago, Lily started the Pet Theory chain of veterinary clinics. As the chain of clinics has grown, Lily spends more time on the phone with insurance companies than treating pets. If only the insurance companies could see the totals of the treatments online!

In previous labs in this series, Ruby, the computer consultant, and Patrick, the DevOps Engineer, moved Pet Theory's customer database to a serverless Firestore database in the cloud, and then opened up access so customers can make appointments online. Since Pet Theory's Ops team is a single person, they need a serverless solution that doesn't require a lot of ongoing maintenance.

In this lab, you'll help Ruby and Patrick to give insurance companies access to customer data without exposing Personal Identifiable Information (PII). You will build a secure Representational State Transfer (REST) API gateway using Cloud Run, which is serverless. This will let the insurance companies see the total cost of treatments without seeing customers' PII.

## **Objectives**

In this lab, you will:

*   Develop a REST API with Go
    
*   Import test customer data into Firestore
    
*   Connect the REST API to the Firestore database
    
*   Deploy the REST API to Cloud Run
    

## **Prerequisites**

This is a **intermediate level** lab. This assumes familiarity with the Cloud Console and Cloud Shell environments. This lab is part of a series. Taking the previous labs could be helpful, but is not necessary:

*   Importing Data to a Serverless Database
    
*   Build a Serverless Web App with Firebase and Firestore
    
*   Build a Serverless App that Creates PDF Files
    

## **Setup and requirements**

### Before you click the Start Lab button

Read these instructions. Labs are timed and you cannot pause them. The timer, which starts when you click **Start Lab**, shows how long Google Cloud resources will be made available to you.

This hands-on lab lets you do the lab activities yourself in a real cloud environment, not in a simulation or demo environment. It does so by giving you new, temporary credentials that you use to sign in and access Google Cloud for the duration of the lab.

To complete this lab, you need:

*   Access to a standard internet browser (Chrome browser recommended).
    

**Note:** Use an Incognito or private browser window to run this lab. This prevents any conflicts between your personal account and the Student account, which may cause extra charges incurred to your personal account.

*   Time to complete the lab---remember, once you start, you cannot pause a lab.
    

**Note:** If you already have your own personal Google Cloud account or project, do not use it for this lab to avoid extra charges to your account.

### How to start your lab and sign in to the Google Cloud console

1.  Click the **Start Lab** button. If you need to pay for the lab, a pop-up opens for you to select your payment method. On the left is the **Lab Details** panel with the following:
    
    *   The **Open Google Cloud console** button
        
    *   Time remaining
        
    *   The temporary credentials that you must use for this lab
        
    *   Other information, if needed, to step through this lab
        
2.  Click **Open Google Cloud console** (or right-click and select **Open Link in Incognito Window** if you are running the Chrome browser).
    
    The lab spins up resources, and then opens another tab that shows the **Sign in** page.
    
    ***Tip:*** Arrange the tabs in separate windows, side-by-side.
    
    **Note:** If you see the **Choose an account** dialog, click **Use Another Account**.
    
3.  If necessary, copy the **Username** below and paste it into the **Sign in** dialog.
    
    ```apache
    student-02-2dc5ad8f062a@qwiklabs.net
    ```
    
    You can also find the **Username** in the **Lab Details** panel.
    
4.  Click **Next**.
    
5.  Copy the **Password** below and paste it into the **Welcome** dialog.
    
    ```apache
    6NMwxpmYkkgO
    ```
    
    You can also find the **Password** in the **Lab Details** panel.
    
6.  Click **Next**.
    
    **Important:** You must use the credentials the lab provides you. Do not use your Google Cloud account credentials.
    
    **Note:** Using your own Google Cloud account for this lab may incur extra charges.
    
7.  Click through the subsequent pages:
    
    *   Accept the terms and conditions.
        
    *   Do not add recovery options or two-factor authentication (because this is a temporary account).
        
    *   Do not sign up for free trials.
        

After a few moments, the Google Cloud console opens in this tab.

**Note:** To view a menu with a list of Google Cloud products and services, click the **Navigation menu** at the top-left.

![Navigation menu icon](https://cdn.qwiklabs.com/nUxFb6oRFr435O3t6V7WYJAjeDFcrFb16G9wHWp5BzU%3D align="left")

### Activate Cloud Shell

Cloud Shell is a virtual machine that is loaded with development tools. It offers a persistent 5GB home directory and runs on the Google Cloud. Cloud Shell provides command-line access to your Google Cloud resources.

1.  Click **Activate Cloud Shell**
    
    ![Activate Cloud Shell icon](https://cdn.qwiklabs.com/ep8HmqYGdD%2FkUncAAYpV47OYoHwC8%2Bg0WK%2F8sidHquE%3D align="left")
    
    at the top of the Google Cloud console.
    

When you are connected, you are already authenticated, and the project is set to your **Project\_ID**, `qwiklabs-gcp-03-334128e6c3bc`. The output contains a line that declares the **Project\_ID** for this session:

```apache
Your Cloud Platform project in this session is set to qwiklabs-gcp-03-334128e6c3bc
```

`gcloud` is the command-line tool for Google Cloud. It comes pre-installed on Cloud Shell and supports tab-completion.

2.  (Optional) You can list the active account name with this command:
    

```apache
gcloud auth list
```

3.  Click **Authorize**.
    

**Output:**

```apache
ACTIVE: *
ACCOUNT: student-02-2dc5ad8f062a@qwiklabs.net

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
```

4.  (Optional) You can list the project ID with this command:
    

```apache
gcloud config list project
```

**Output:**

```apache
[core]
project = qwiklabs-gcp-03-334128e6c3bc
```

**Note:** For full documentation of `gcloud`, in Google Cloud, refer to [the gcloud CLI overview guide](https://cloud.google.com/sdk/gcloud).

| *Lily, Founder of Pet Theory* | Hi Ruby, |
| --- | --- |
| *Ruby, Software Consultant* | Hi Lily, |

Help Ruby manage the activities necessary to build the REST API for Pet Theory.

## **Task 1. Enable Google APIs**

For this lab, 2 APIs have been enabled for you:

| **Name** | **API** |
| --- | --- |
| Cloud Build | cloudbuild.googleapis.com |
| Cloud Run | run.googleapis.com |

## **Task 2. Developing the REST API**

1.  Activate your project:
    

```apache
gcloud config set project $(gcloud projects list --format='value(PROJECT_ID)' --filter='qwiklabs-gcp')
```

2.  Clone the pet-theory repository and access the source code:
    

```apache
git clone https://github.com/rosera/pet-theory.git && cd pet-theory/lab08
```

3.  Use your favorite text editor, or use the Code Editor button in the Cloud Shell ribbon, to view the `go.mod` and `go.sum` files.
    
4.  Create the file `main.go` and add the below contents to the file:
    

```python
package main

import (
  "fmt"
  "log"
  "net/http"
  "os"
)

func main() {
  port := os.Getenv("PORT")
  if port == "" {
      port = "8080"
  }
  http.HandleFunc("/v1/", func(w http.ResponseWriter, r *http.Request) {
      fmt.Fprintf(w, "{status: 'running'}")
  })
  log.Println("Pets REST API listening on port", port)
  if err := http.ListenAndServe(":"+port, nil); err != nil {
      log.Fatalf("Error launching Pets REST API server: %v", err)
  }
}
```

**Note:** In the above code, you create an endpoint to test that the service is up and running as expected. By appending "/v1/" to the service URL, you can verify the application is functioning as expected. Cloud Run deploys containers, so you need to provide a container definition. A file named `Dockerfile` tells Cloud Run which Go version to use, which files to include in the app, and how to start the code.

5.  Now create a file named `Dockerfile` and add the following to it:
    

```dockerfile
FROM gcr.io/distroless/base-debian12
WORKDIR /usr/src/app
COPY server .
CMD [ "/usr/src/app/server" ]
```

The file `server` is the execution binary built from `main.go`.

6.  Run the following command to build the binary:
    

```apache
go build -o server
```

7.  After running the build command, make sure that you have the necessary Dockerfile and server in the same directory:
    

```apache
ls -la
```

```apache
 .
 ├── Dockerfile
 ├── go.mod
 ├── go.sum
 ├── main.go
 └── server
```

For most Cloud Run Go based apps, a template Dockerfile like the one above can typically be used without modifying it.

8.  Deploy your simple REST API by running:
    

```apache
gcloud builds submit \
  --tag gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.1
```

This command builds a container with your code and puts it in the Container Registry of your project. You can see the container if you click: **Navigation menu** > **Container Registry**. If you don't see `rest-api`, click **Refresh**.

![Container Registry](https://cdn.qwiklabs.com/R4rRT8MNbx%2FxjQjJs9bivudtNjfWrmT%2B%2F7mzJGsaBaE%3D align="left")

Click **Check my progress** to verify that you've performed the above task.

Build an image with Cloud Build

**Check my progress**

9.  Once the container has been built, deploy it:
    

```apache
gcloud run deploy rest-api \
  --image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.1 \
  --platform managed \
  --region us-east1 \
  --allow-unauthenticated \
  --max-instances=2
```

10.  When the deployment is complete, you will see a message like this:
     

```apache
Service [rest-api] revision [rest-api-00001] has been deployed and is serving
traffic at https://rest-api-[hash].a.run.app
```

Click **Check my progress** to verify that you've performed the above task.

REST API service deployed

**Check my progress**

11.  Click on the Service URL at the end of that message to open it in a new browser tab. Append `/v1/` to the end of the URL and then press **Enter**.
     

You should see this message:

![{"status" : "running"}](https://cdn.qwiklabs.com/zhNIviSEfrxEV4vyG18wfnSGLorqH9HniUCU8b2ff1s%3D align="left")

The REST API is up and running. With the prototype service available, in the next section the API will be used to retrieve "customer" information from a Firestore database.

## **Task 3. Import test customer data**

| *Ruby, Software Consultant* | Hey Patrick, |
| --- | --- |
| *Patrick, IT Administrator* | Hi Ruby, |

Ruby and Patrick have previously created a test database of 10 customers, with some proposed treatments for one customer's cat.

Help Patrick configure the Firestore database and import the customer test data. First, enable Firestore in your project.

1.  Return to the Cloud Console and click the **Navigation Menu** > **Firestore**.
    
2.  Click the **Create Database** button.
    
3.  Click the **Native Mode** button and click **Continue**.
    
4.  For **Location type** select **Region**.
    
5.  Select the region `us-east1` from the list available and click **Create Database**.
    

Wait for the database to be created before proceeding.

Click **Check my progress** to verify that you've performed the above task.

Firestore database created

**Check my progress**

6.  Migrate the import files into a Cloud Storage bucket that has been created for you:
    

```apache
gsutil mb -c standard -l us-east1 gs://$GOOGLE_CLOUD_PROJECT-customer
```

```apache
gsutil cp -r gs://spls/gsp645/2019-10-06T20:10:37_43617 gs://$GOOGLE_CLOUD_PROJECT-customer
```

7.  Now import this data into Firebase:
    

```apache
gcloud beta firestore import gs://$GOOGLE_CLOUD_PROJECT-customer/2019-10-06T20:10:37_43617/
```

Reload the Cloud Console browser to see the Firestore results.

8.  In Firestore, click **customers** under "Default". You should see the imported pet data, browse around. If you don't see any data, try refreshing the page.
    

Nice work, the Firestore database has been successfully created and populated with test data!

## **Task 4. Connect the REST API to the Firestore database**

| *Ruby, Software Consultant* | Hi Lily, |
| --- | --- |
| *Lily, Founder of Pet Theory* | Hi Ruby, |

In this section you'll help Ruby create another end-point in the REST API that will look like this:

```apache
https://rest-api-[hash].a.run.app/v1/customer/22530
```

For example, that URL should return the total amounts for all proposed, accepted, and rejected treatments for the customer with id 22530, if they exist in the Firestore database:

```json
{
  "status": "success",
  "data": {
    "proposed": 1602,
    "approved": 585,
    "rejected": 489
  }
}
```

**Note:** If the customer doesn't exist in the database, status code 404 (not found) and an error message should be returned instead.

This new functionality requires a package to access the Firestore database and another one to handle cross-origin resource sharing (CORS).

1.  Get the value of the $GOOGLE\_CLOUD\_PROJECT environment variable
    

```apache
echo $GOOGLE_CLOUD_PROJECT
```

2.  Open the existing `main.go` file in the pet-theory/lab08 directory.
    

**Note:** Update the contents of main.go using the value shown for $GOOGLE\_CLOUD\_PROJECT.

3.  Replace the content of the file with the code below, ensure the `PROJECT_ID` is set to `qwiklabs-gcp-03-334128e6c3bc`:
    

```python
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	"cloud.google.com/go/firestore"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"google.golang.org/api/iterator"
)

  var client *firestore.Client

  func main() {
    var err error
    ctx := context.Background()
    client, err = firestore.NewClient(ctx, "qwiklabs-gcp-03-334128e6c3bc")
    if err != nil {
    log.Fatalf("Error initializing Cloud Firestore client: %v", err)
  }

  port := os.Getenv("PORT")
  if port == "" {
    port = "8080"
  }

  r := mux.NewRouter()
  r.HandleFunc("/v1/", rootHandler)
  r.HandleFunc("/v1/customer/{id}", customerHandler)

  log.Println("Pets REST API listening on port", port)
  cors := handlers.CORS(
    handlers.AllowedHeaders([]string{"X-Requested-With", "Authorization", "Origin"}),
    handlers.AllowedOrigins([]string{"https://storage.googleapis.com"}),
    handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "OPTIONS", "PATCH", "CONNECT"}),
  )

	if err := http.ListenAndServe(":"+port, cors(r)); err != nil {
    log.Fatalf("Error launching Pets REST API server: %v", err)
	}
}
```

4.  Add handler support at the bottom of the file:
    

```python
func rootHandler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "{status: 'running'}")
}

func customerHandler(w http.ResponseWriter, r *http.Request) {
  id := mux.Vars(r)["id"]
  ctx := context.Background()
  customer, err := getCustomer(ctx, id)
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintf(w, `{"status": "fail", "data": '%s'}`, err)
    return
  }
  if customer == nil {
    w.WriteHeader(http.StatusNotFound)
    msg := fmt.Sprintf("`Customer \"%s\" not found`", id)
    fmt.Fprintf(w, fmt.Sprintf(`{"status": "fail", "data": {"title": %s}}`, msg))
    return
  }
  amount, err := getAmounts(ctx, customer)
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}`, err)
    return
  }
  data, err := json.Marshal(amount)
  if err != nil {
    w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}`, err)
    return
  }
  fmt.Fprintf(w, fmt.Sprintf(`{"status": "success", "data": %s}`, data))
}
```

5.  Add Customer support to the bottom of the file:
    

```python
type Customer struct {
  Email string `firestore:"email"`
  ID    string `firestore:"id"`
  Name  string `firestore:"name"`
  Phone string `firestore:"phone"`
}

func getCustomer(ctx context.Context, id string) (*Customer, error) {
  query := client.Collection("customers").Where("id", "==", id)
  iter := query.Documents(ctx)

  var c Customer
  for {
    doc, err := iter.Next()
    if err == iterator.Done {
	break
    }
    if err != nil {
	return nil, err
    }
    err = doc.DataTo(&c)
    if err != nil {
	return nil, err
    }
  }
  return &c, nil
}

func getAmounts(ctx context.Context, c *Customer) (map[string]int64, error) {
  if c == nil {
    return map[string]int64{}, fmt.Errorf("Customer should be non-nil: %v", c)
  }
  result := map[string]int64{
    "proposed": 0,
    "approved": 0,
    "rejected": 0,
  }
  query := client.Collection(fmt.Sprintf("customers/%s/treatments", c.Email))
  if query == nil {
    return map[string]int64{}, fmt.Errorf("Query is nil: %v", c)
  }
  iter := query.Documents(ctx)
  for {
    doc, err := iter.Next()
    if err == iterator.Done {
	break
    }
    if err != nil {
	return nil, err
    }
    treatment := doc.Data()
    result[treatment["status"].(string)] += treatment["cost"].(int64)
  }
  return result, nil
}
```

6.  **Save** the file.
    

## **Task 6. Pop quiz**

Which function responds to URLs with the pattern `/v1/customer/`customerHandlergetAmounts

**Submit**

Which statement returns success to the clientfmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"}fmt.Fprintf(w, fmt.Sprintf(`{"status": "success", "data": %s}

**Submit**

Which functions read from the Firestore databasecustomerHandler and getCustomergetCustomer and getAmounts

**Submit**

## **Task 7. Deploying a new revision**

1.  Rebuild the source code:
    

```apache
go build -o server
```

2.  Build a new image for the REST API:
    

```apache
gcloud builds submit \
  --tag gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.2
```

Click **Check my progress** to verify the objective.

Build image revision 0.2

**Check my progress**

3.  Deploy the updated image:
    

```apache
gcloud run deploy rest-api \
  --image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.2 \
  --platform managed \
  --region us-east1 \
  --allow-unauthenticated \
  --max-instances=2
```

Service \[rest-api\] revision \[rest-api-00002\] has been deployed and is serving traffic at https://rest-api-\[hash\].a.run.app

5.  Go back to the browser tab that already points to that URL (with `/v1/` at the end). Refresh it and make sure you get the same message as before, that indicates that the API status is still running.
    

![{status" : "running"}](https://cdn.qwiklabs.com/Q6zP6oeJbelXp7M24egmn5kOqwM%2FjM2udtHAx9S9k9c%3D align="left")

6.  Append `/customer/22530` to the application URL in your browser's address bar. You should get this JSON response, listing the sum total of the customer's proposed, approved and rejected treatments:
    

![{"status" : "success", "data" :{"proposed" :1602, "approved" :585, "reected" :489}}](https://cdn.qwiklabs.com/YA7BORLOP0WqHewA%2B2vxM2Gll12QkAbxZ%2F7PN2IblYI%3D align="left")

7.  Here are some additional client IDs you can put in the URL instead of 22530:
    

*   34216
    
*   70156 (all amounts should be zero)
    
*   12345 (client/pet doesn't exist, should return an error e.g. **Query is nil**)
    

You have built a scalable, low-maintenance, serverless REST API that reads from a database.

* * *

## Solution of Lab

%[https://www.youtube.com/watch?v=_oAgkWCeNrE] 

```apache
curl -LO raw.githubusercontent.com/ePlus-DEV/storage/refs/heads/main/labs/GSP761/lab.sh
source lab.sh
```

**Script Alternative**

```apache
export REGION=
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1724073703136/a0c2d731-eeab-4024-88fb-464232c4f8c7.png align="center")

```apache
curl -LO raw.githubusercontent.com/quiccklabs/Labs_solutions/master/Developing%20a%20REST%20API%20with%20Go%20and%20Cloud%20Run/quicklabgsp761.sh
sudo chmod +x quicklabgsp761.sh
./quicklabgsp761.sh
```
