arrow_back

Developing a REST API with Go and Cloud Run

Join Sign in

Developing a REST API with Go and Cloud Run

1 hour 5 Credits

GSP761

Google Cloud self-paced labs logo

Pet Theory logo

Overview

In this lab you will read through the scenario, then take the steps the characters take to solve the business problem.

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.

Prerequisites

This is a fundamental 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

You should also be comfortable editing files. You can use your favorite text editor (like nano, vi, etc.) or you can launch the code editor from Cloud Shell, which can be found from the top ribbon:

Open Editor button

Setup

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 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 Console. 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 from the Lab Details panel and paste it into the Sign in dialog. Click Next.

  4. Copy the Password from the Lab Details panel and paste it into the Welcome dialog. Click Next.

    Important: You must use the credentials from the left panel. Do not use your Google Cloud Skills Boost credentials. Note: Using your own Google Cloud account for this lab may incur extra charges.
  5. 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 Cloud Console opens in this tab.

Note: You can view the menu with a list of Google Cloud Products and Services by clicking the Navigation menu at the top-left. Navigation menu icon

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 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. The output contains a line that declares the PROJECT_ID for this session:

Your Cloud Platform project in this session is set to YOUR_PROJECT_ID

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

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

gcloud auth list
  1. Click Authorize.

  2. Your output should now look like this:

Output:

ACTIVE: * ACCOUNT: student-01-xxxxxxxxxxxx@qwiklabs.net To set the active account, run: $ gcloud config set account `ACCOUNT`
  1. (Optional) You can list the project ID with this command:

gcloud config list project

Output:

[core] project = <project_ID>

Example output:

[core] project = qwiklabs-gcp-44776a13dea667a6 Note: For full documentation of gcloud, in Google Cloud, refer to the gcloud CLI overview guide.

Lily

Lily, Founder of Pet Theory

Hi Ruby,

Remember our conversation last week when I expressed how swamped I am with paperwork and phone calls from the insurance company? If only there was a way to allow the representatives to access customer records in an efficient, secure way.

This current level of workload isn't sustainable. Can you help?

Lily

Ruby

Ruby, Software Consultant

Hi Lily,

Yesterday I had lunch with Patrick and we drew up a plan to make it easier for authorized 3rd parties to securely access Pet Theory's digital records.

We will build this in four steps:

  1. Build a simple REST API.
  2. Import customer test data.
  3. Connect the REST API to the customer database.
  4. Add authentication to the REST API.

Patrick and I already have the skillset for steps 1 + 2, so we are off to a good start. We plan to have a working prototype by the end of the week.

Ruby

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:

gcloud config set project $(gcloud projects list --format='value(PROJECT_ID)' --filter='qwiklabs-gcp')
  1. Clone the pet-theory repository and access the source code:

git clone https://github.com/rosera/pet-theory.git && cd pet-theory/lab08
  1. 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.

  2. Create the file main.go and add the below contents to the file:

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.
  1. Now create a file named Dockerfile and add the following to it:

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

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

  1. Run the following command to build the binary:

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

ls -la . ├── 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.

  1. Deploy your simple REST API by running:

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

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

Build an image with Cloud Build
  1. Once the container has been built, deploy it:

gcloud run deploy rest-api \ --image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.1 \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --max-instances=2
  1. When the deployment is complete, you will see a message like this:
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
  1. 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"}

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

Ruby, Software Consultant

Hey Patrick,

Do you still have the pseudo customer data we created a while back? We will need that for testing.

Do you remember how to set up a Firestore database and import data?

Ruby

Patrick

Patrick, IT Administrator

Hi Ruby,

Yes, I still have the test data. I will migrate it to Firestore today so you can use it for testing.

Patrick

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 Select Native Mode button.

  3. Select the location "nam5" (United States) multi-region near the top of the list.

  4. Click the Create Database button.

  5. Wait for the database to be created.

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

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

gsutil cp -r gs://spls/gsp645/2019-10-06T20:10:37_43617 gs://$GOOGLE_CLOUD_PROJECT-customer
  1. Now import this data into Firebase:

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.

  1. In Firestore, click customers under "Root". 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

Ruby, Software Consultant

Hi Lily,

Just a quick update: Patrick and I have completed the first two tasks on the list.

Now I'm moving on to structuring the REST API so it can access the customer data in Firestore.

Ruby

Lily

Lily, Founder of Pet Theory

Hi Ruby,

Great work, Ruby! Looking forward to seeing the next stage in action.

Lily

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

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:

{ "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
echo $GOOGLE_CLOUD_PROJECT
  1. 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.
  1. Replace the content of the file with the code below, and then replace the PROJECT_ID:

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, "PROJECT_ID") 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) } }
  1. Add handler support at the bottom of the file:

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)) }
  1. Add Customer support to the bottom of the file:

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 }
  1. Save the file.

Task 6. Pop quiz

Which function responds to URLs with the pattern `/v1/customer/` getAmounts customerHandler Which statement returns success to the client fmt.Fprintf(w, `{"status": "fail", "data": "Unable to fetch amounts: %s"} fmt.Fprintf(w, fmt.Sprintf(`{"status": "success", "data": %s} Which functions read from the Firestore database customerHandler and getCustomer getCustomer and getAmounts

Task 7. Deploying a new revision

  1. Rebuild the source code:

go build -o server
  1. Build a new image for the REST API:

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

  1. Deploy the updated image:

gcloud run deploy rest-api \ --image gcr.io/$GOOGLE_CLOUD_PROJECT/rest-api:0.2 \ --platform managed \ --region us-central1 \ --allow-unauthenticated \ --max-instances=2
  1. When the deployment is complete, you will see a similar message to before. The URL for your REST API did not change when you deployed the new version:
Service [rest-api] revision [rest-api-00002] has been deployed and is serving traffic at https://rest-api-[hash].a.run.app
  1. 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"}

  1. 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}}

  1. 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.

Congratulations!

Ruby and Patrick successfully built a prototype REST API for Pet Theory.

Finish your quest

This self-paced lab is part of the Google Cloud Run Serverless Workshop quest. A quest is a series of related labs that form a learning path. Completing this quest earns you a badge to recognize your achievement. You can make your badge or badges public and link to them in your online resume or social media account. Enroll in this quest and get immediate completion credit. Refer to the Google Cloud Skills Boost catalog for all available quests.

Take your next lab

Continue your quest with the next lab in the series, Creating PDFs with Go and Cloud Run.

Google Cloud training and certification

...helps you make the most of Google Cloud technologies. Our classes include technical skills and best practices to help you get up to speed quickly and continue your learning journey. We offer fundamental to advanced level training, with on-demand, live, and virtual options to suit your busy schedule. Certifications help you validate and prove your skill and expertise in Google Cloud technologies.

Manual Last Updated: August 29, 2022

Lab Last Tested: December 22, 2021

Copyright 2022 Google LLC All rights reserved. Google and the Google logo are trademarks of Google LLC. All other company and product names may be trademarks of the respective companies with which they are associated.