Build Go Restful API in App Engine

Evan Tsai

Restful APIs are essentials for communcation between backend and frontend services. This post aims to show a simple scenario that builds up the API service in Golang. Also, we will use Google Cloud Platform to spin up the service in a few minutes.

API Overview

Our API will provide an endpoint to insert a data object called Booking into SQL database. The url will be localhost:8080/v1/booking. We use v1 to make the service backward compatible. In other words, the service will maintain older versions, and each version is accessed via different urls.

In the example, we will only use http PUT method as objects are not specified in the url. If using POST method, the url should be /booking/{item_id}, where we specify what object needs update.

A Booking object represented in JSON. This JSON object will be included in an API request’s body.

{
"orderid": "SAC22000811281",
"sid":"C7725168",
"name": "TestName",
"rd1": "A2",
"createDate": "2010/12/10",
"phone": "09876123123",
"phone2": "123123907",
"email": "123@ase.com",
"rd3": "day",
"rd4": "ATM",
"address": "test.comalsd",
"note": "test Note"
}

Since we are sending JSON format payload, the header of a request has to include this entry. Our server will parse the payload in this specific format.

Content-Type: application/json

Go Server

To build a server with least difficulty, we will use go-chi to manage routing, in which the main router directs requests into multiple sub-routers based on their urls. E.g. /booking is handled in BookingRouter. For generating responses and processing requests payloads, we will use go-chi/render.

  1. Create the main router
func Routes() *chi.Mux {
router := chi.NewRouter()
router.Use(
render.SetContentType(render.ContentTypeJSON),
middleware.Logger,
middleware.DefaultCompress,
middleware.RedirectSlashes,
middleware.Recoverer,
)
// add sub-routers here
// sub-router url started with prefix v1
router.Route("/v1", func(r chi.Router) {
r.Mount("/booking", BookingRoutes())
})

return router
}

2. Create a sub router

func BookingRoutes() *chi.Mux {
router := chi.NewRouter()
// url, and the func to invoke
router.Put("/", CreateBooking)
return router
}
// Handles the request
func CreateBooking(w http.ResponseWriter, r *http.Request) {

data := &reqmodel.BookingRequest{}
// Binding decodes the json content and put the result in a
// Booking
if err := render.Bind(r, data); err != nil {
render.Render(w, r, networkError.ErrInvalidRequest(err))
return
}

booking := data.Booking
// Handles logic here
database.InsertBooking(booking)

// generates a response with 201 status code and json-encoded
// booking data
render.Status(r, http.StatusCreated)
render.Render(w, r, reqmodel.NewBookingResponse(booking))
}

3. Application entry point

To run in App Engine, we need to put a main package in the root folder of a project. And the func main must be included.

package mainfunc main() {

router := routes.Routes()

walkFunc := func(method string, route string,
handler http.Handler,
middlewares ...func(http.Handler) http.Handler) error {
return nil
}
if err := chi.Walk(router, walkFunc); err != nil {
log.Panicf("Logging err: %s\n", err.Error())
}

log.Fatal(http.ListenAndServe(":8080", router))

}

In the func main, we get our main router, and provide a walk function to log any error (which is optional). To start the server, invoke http.ListenAndServe with the port number.

4. Deploy to App Engine

Prerequisites

  • Google Cloud Platform account with App Engine enabled
  • Google Cloud SDK installed locally (or use Cloud terminal)

Here, we will use the Google Cloud SDK to upload the App locally. Once you set up the Cloud SDK, invoke the following command in the project root folder.

gcloud app deploy

Once it’s deployed, you should be able to send requests to the shown url and get a successful response.

Data Model Object: Booking

Here, we will talk about how to set up Booking and how we generate BookingResponse.

  1. What booking looks like?
package model

type Booking struct {
OrderID string `json:"orderid,omitempty"`
SID string `json:"sid,omitempty"`
Name string `json:"name"`
RD1 string `json:"rd1"`
CreateDate string `json:"createDate"`
PostDate string `json:"postDate"`
Phone string `json:"phone"`
Phone2 string `json:"phone2"`
Email string `json:"email"`
RD3 string `json:"rd3"`
RD4 string `json:"rd4"`
Address string `json:"address"`
Note string `json:"note"`
}

Between two backticks is a tag. A tag is used to specify metadata for this particular property. In this case, tags are useful for JSON decoding & encoding.

2. How to read requests?

type BookingRequest struct {
*model.Booking
}

func (a *BookingRequest) Bind(r *http.Request) error {
// a.Booking is nil if no Booking fields are sent in the request.
// Return an error to avoid a nil pointer dereference.
if a.Booking == nil {
return errors.New("missing required booking fields")
}
return nil
}

3. How to generate requests?

type BookingResponse struct {
*model.Booking

// We add an additional field to the response here.. such as this
// elapsed computed property
Elapsed int64 `json:"elapsed"`
}
// Create a response for a booking
func NewBookingResponse(booking *model.Booking) *BookingResponse {
resp := &BookingResponse{Booking: booking}
return resp
}

// Render booking response
func (rd *BookingResponse) Render(w http.ResponseWriter, r *http.Request) error {
// Pre-processing before a response is marshalled and sent across the wire
rd.Elapsed = 10
return nil
}

Saving data into SQL Database

Remember we invoke function to save data when handling requests? In the next post, we will discuss how to save a record in Google Cloud SQL.

Evan Tsai

Written by

Evan Tsai

iOS Engineer | Moving to be a Software Architect

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade