Let’s Go, Everything you need to know about creating a RESTful Api in Go — Part V

Supun Muthutantrige
6 min readMay 12, 2019

--

Detailed overview on consuming an external REST API’s

PART V

Most of the API’s that are there requires consuming other external APIs within the application. Depending on the complexity, an application may consume multiple external services as required. This article is about learning how to make our go application consume external API endpoints which could be a GET, POST, PUT, PATCH or DELETE.

First will discuss the service layer, then integrate the service layer with the application control layer and finally trigger our application endpoints through POSTMAN to see our implementation in action.

Let’s create the service layer. Will create a separate package to host service implementations.

[Folder structure]go/
|_ bin
|_ pkg
|_ src
|_ app
|_ app.go
|_ service
|_ employeeService.go

What’s bold above is the new package for our application. Here, employeeService is responsible for client side implementation of the external API we are going to consume. For ease, I’ll be using an already available dummy API which can be found in here. I’ll be describing about integrating Get all employee data and create a new employee REST API endpoints.

[employeeService.go]package serviceimport (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
)
type Employees []Employee//Employee response object
//{
// "id": "719",
// "employee_name": "test",
// "employee_salary": "123",
// "employee_age": "23",
// "profile_image": "",
//}
type Employee struct {
Id string `json:"id"`
Name string `json:"employee_name"`
Salary string `json:"employee_salary"`
Age string `json:"employee_age"`
Image string `json:"profile_image"`
}
// simple GET implementation
func DoSimpleGet() Employees {
log.Print("Start fetching data from service")
response, err :=
http.Get("http://dummy.restapiexample.com/api/v1/employees")
if err != nil {
log.Panic(err.Error())
}
defer response.Body.Close() responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var res Employees
json.Unmarshal(responseData, &res)
log.Print("Data fetched from service: ", string(res))
log.Print("Leaving fetch data from service")
return res
}

Function DoSimpleGet() implementation provides the details on integrating the GET endpoint. We’ve used the internal http package to consume external services. Function http.GET(url string) requires passing the url parameter. Then we validate the output for any errors, next we should defer response.Body.Close() which will close the response body once all the other tasks are done. Line, ioutil.ReadAll(response.Body) reads the returned values in response.Body till the EOF and returns a []byte. Now we have a []byte which needs to map to a certain struct. We have created a struct called Employees which is another struct comprised of a list of Employee structs. Line, json.Unmarshal(responseData, &res) takes the previously returned byte array and the address location of Employees struct, then unmarshal data which will end up populating the Employees struct from the json response returned in the Get endpoint. Finally we return the populated Employees struct.

Similarly, the function with the post request implementation is as follows,

[employeeService.go]
...
// simple POST request
func DoSimplePost() CreateEmployee {
log.Print("Initiate post request to service")
jsonData := map[string]string{"name": "name1115", "salary":
"123", "age": "23"}
jsonValue, _ := json.Marshal(jsonData)
response, err := http.Post(
"http://dummy.restapiexample.com/api/v1/create",
"application/json",
bytes.NewBuffer(jsonValue))
if err != nil {
log.Panic(err.Error())
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
var responseObject CreateEmployee
json.Unmarshal(data, &responseObject)
log.Print("Data created from service: ", responseObject) return responseObject
}

The difference in post request is the payload and header values we send. Here, we’ve hard coded the request payload (body). Json payload is generated via a string key, value map, which is then marshalled using json.Marshal(). Compared to get method, http.Post() requires passing contentyType and body parameters along with the url. Other parts of the method are more or less the same as in the get method, where once the post request is sent, it accepts the response, unmarshal it and populate the createEmployee struct.

When looking at both get and post methods, it is clear that there are some common expressions used. Such as, post method contains two extra parameters compared to get and other error handling and unmarshalling can be considered common to both approaches.

We can extract the common parts and restructure above code as follows,

[employeeService.go]package serviceimport (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"log"
"net/http"
)
type Employees []Employee//Create Employee response object
//{
// "id": "719",
// "name": "test",
// "salary": "123",
// "age": "23",
//}
type CreateEmployee struct {
Id string `json:"id"`
Name string `json:"name"`
Salary string `json:"salary"`
Age string `json:"age"`
}
//Employee response object
//{
// "id": "719",
// "employee_name": "test",
// "employee_salary": "123",
// "employee_age": "23",
// "profile_image": "",
//}
type Employee struct {
Id string `json:"id"`
Name string `json:"employee_name"`
Salary string `json:"employee_salary"`
Age string `json:"employee_age"`
Image string `json:"profile_image"`
}
// get request
func DoGet() Employees {
log.Print("Start fetching data from service")
response := doApiRequest(
"GET",
"http://dummy.restapiexample.com/api/v1/employees",
nil
)
var responseObject Employees
json.Unmarshal(response, &responseObject)
log.Print("Data fetched from service: ", responseObject) return responseObject
}
// post request
func DoPost() CreateEmployee {
log.Print("Start create request")
jsonData := map[string]string{"name": "test10", "salary": "123",
"age": "23"}
jsonValue, _ := json.Marshal(jsonData)
response := doApiRequest(
"POST",
"http://dummy.restapiexample.com/api/v1/create",
bytes.NewBuffer(jsonValue)
)
var responseObject CreateEmployee
json.Unmarshal(response, &responseObject)
log.Print("Data received from post request: ", responseObject) return responseObject
}
// common method
func doApiRequest(method string, url string, body io.Reader) []byte {
client := &http.Client{}
req, err := http.NewRequest(method, url, body)
if err != nil {
log.Panic(err.Error())
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
response, err := client.Do(req) if err != nil {
log.Panic(err.Error())
}
defer response.Body.Close() responseData, err := ioutil.ReadAll(response.Body)
if err != nil {
log.Fatal(err)
}
log.Print("Return service response: ", string(responseData))
return responseData
}

I have restructured the previous code to create a common method which can be used whether it is a Get, Post, Put, Patch or Delete. The common method doApiRequest uses a http.Client{} which takes a http.NewRequest where we can pass the method as a string. For ourget method NewRequest contains the method name "Get", url and the body parameter as nil (since GET doesn’t contain a request body in this case). In comparison, the post method would contain the name as "Post", url and the body which would be passed in. Next, our common method assigns the header values. Here, you can specify any header values required by the external service. A good practice is to pass the header values necessary per each http method, which is hard coded in this instance. Once the request is ready, the service invocation happens by executing, client.Do(request). The rest of the method is to get the response, convert it to a byte array to return to the source method.

I have changed previously used two methods DoSimpleGet and DoSimplePost as DoGet and DoPost which uses the common doApiRequest method internally. The primary objective of both DoGet and DoPost methods are to prepare the data which is required by the common method as well as handle its own responsibility. Further, these two methods will be exposed to the controller layer.

Now we have completed our service layer implementation, let’s create two new endpoints from the controller layer, which should use above created service layer methods to invoke the external APIs.

[app.go]package mainimport (
"encoding/json"
"log"
"net/http"
"service"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/employee", fetchEmployeeHandler).Methods("GET") r.HandleFunc("/employee", createEmployeeHandler).Methods("POST") ... log.Fatal(http.ListenAndServe(":8080", r))
}
func fetchEmployeeHandler(w http.ResponseWriter, r *http.Request) {
log.Print("Request received to fetch list of employees")
//fetch list of employees
listOfEmployees := service.DoGet()
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(listOfEmployees)
}
func createEmployeeHandler(w http.ResponseWriter, r *http.Request) {
log.Print("Request received to create an employee")
//create employee
employee := service.DoPost()
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(employee)
}
...

Two handler methods fetchEmployeeHandler and createEmployeeHandler are used herein, which are exposed via new Getand Post REST API endpoints of our application. The fetchEmployeeHandler method is responsible for invoking previously implemented DoGet method which will return a list of Employees from the external service. Similarly, createEmployeeHandler method use DoPost service method to invoke the post request which will create a new employee using the external API.

Let’s trigger our endpoints using Postman and see the results.

response from GET method invocation
response from POST method invocation

As you can see we get the responses from our application as intended which consumes the external APIs within the application.

It’s the end of another article. Up until now we have completed most of the things that you would require when creating a Go REST API. Thus far we’ve learnt,

  • How to expose a simple API
  • How to integrate swagger documentation
  • How to connect to a database
  • How to consume a 3rd party REST API’s within our go application.

For now that’s it. See you soon with another article.

check my personal blog to view more — icodeforpizza

Prev Section — Part IV

--

--