The Guide to Build and Dockerize a Go App
Tags: docker, go, devops, cloud
Published: 2022-05-10 05:12:44

Hey guys, welcome to our tutorial. The verb Dockerize might sound unfamiliar for some, just to be in harmony, the verb simply means that we will pack, deploy and run our Go application using Docker containers. 

Containers are units of software that package up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. In other words, all the necessary tools to build our Go Application will be piled together with the application itself to make one single portable and standard module; which could be accessible anywhere and run on various operating systems.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/anu2umc9uxtihlmd9pr2.gif)

###Prerequisites
The first prerequisite for this tutorial is to have a basic knowledge of Go programming and second, to have the following:
- Docker installed on our computers,
- Docker Hub account,
- Go Runtime installed,
- RapidAPI account and subscription to Alpha Vantage API

##1. Go Application

This app is going to be an API that fetches stock market information of a number of listed companies. It will send a GET request to Alpha Vantage API which is the most effective way to receive stock data. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hg4nhysddjt6h96f71q7.png)

As designed in the scenario above, a client will make a GET request with a query parameter to the server running in the local machine. Then The server will take that query parameter to make another GET request now to the Alpha Vantage API, which will return stock data related to the query.

> **Note**: Open this [link](https://rapidapi.com/alphavantage/api/alpha-vantage/) to sign up with Rapid API and subscribe to Alpha Vantage API.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xtdttu8d9neam85rgjiv.png)

Now, let's dive into coding. First, create a directory named `stock-list` and move inside:
```bash
$ mkdir stock-list
$ cd stock-list/
```
Run this command to manage internal packages or dependencies:
```bash
$ go mod init stock-list
```
And run these commands to install the dependencies to be used during this tutorial:
```bash
$ go get github.com/gin-gonic/gin
$ go get github.com/rodaine/table
```
Create a file named `stock.go`, open it and put the code snippet below:
```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/rodaine/table"
)

type Quote struct {
	SYMBOL string `json:"01. symbol"`
	OPEN string `json:"02. open"`
	HIGH string `json:"03. high"`
	LOW string `json:"04. low"`
	PRICE string `json:"05. price"`
	VOLUME  string `json:"06. volume"`
	LATEST_TRADING_DAY string `json:"07. latest trading day"`
	PREVIOUS_CLOSE string `json:"08. previous close"`
	CHANGE string `json:"09. change"`
	CHANGE_PERCENT string `json:"10. change percent"`
}

type Stock struct {
	DETAIL Quote `json:"Global Quote"`
}

func getStock(s string, ch chan Stock) Stock{
	var stock Stock

	
	api := "https://alpha-vantage.p.rapidapi.com/query?function=GLOBAL_QUOTE&symbol="+s+"&datatype=json"

	req, _ := http.NewRequest("GET", api, nil);

	req.Header.Add("X-RapidAPI-Host", "alpha-vantage.p.rapidapi.com")
	req.Header.Add("X-RapidAPI-Key", "Here put your RapidAPI Key")

	res, err := http.DefaultClient.Do(req)

	if err != nil {
		fmt.Println("No response from request")
	}

	defer res.Body.Close()

	body, _ := ioutil.ReadAll(res.Body)

	
	if err := json.Unmarshal(body, &stock); err != nil {
		fmt.Println("Can not unmarshal JSON")
	}
	ch<- stock
	return stock
}

func genTable(watchList []Quote){
	
	tbl := table.New("SYMBOL", "OPEN", "HIGH", "LOW", "PRICE", "VOLUME", "PREVIOUS-CLOSE", "CHANGE")

	for _, stock := range watchList {
		tbl.AddRow(stock.SYMBOL, stock.OPEN, stock.HIGH, stock.LOW, stock.PRICE, stock.VOLUME, stock.PREVIOUS_CLOSE, stock.CHANGE)
	}

	tbl.Print()

}
```
###1.1 Code overview 1
In the code above, we did the following:
**1. Imported a couple of packages with**
```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/rodaine/table"
)
```
This section of the file imports `fmt` that prints string to the console, `github.com/rodaine/table` which generates table from the stock data, `net/http` is used to make HTTP requests to Alpha Vantage API, `io/ioutil` is used to read stock data of the response body from the Alpha Vantage and `encoding/json` to convert the json stock data (which is in []byte) into our object struct.

**2. Created structs for stock data**
```go
type Quote struct {
	SYMBOL string `json:"01. symbol"`
	OPEN string `json:"02. open"`
	HIGH string `json:"03. high"`
	LOW string `json:"04. low"`
	PRICE string `json:"05. price"`
	VOLUME  string `json:"06. volume"`
	LATEST_TRADING_DAY string `json:"07. latest trading day"`
	PREVIOUS_CLOSE string `json:"08. previous close"`
	CHANGE string `json:"09. change"`
	CHANGE_PERCENT string `json:"10. change percent"`
}

type Stock struct {
	DETAIL Quote `json:"Global Quote"`
}

```
The struct `Quote` basically represents the property value of the stock data contained in the response body returned by the Alpha Vantage. `Stock` struct is a nested struct that embodies the key and the value of the response.

**3. Created the getStock() function**
```go
func getStock(s string, ch chan Stock) Stock{
	var stock Stock

	
	api := "https://alpha-vantage.p.rapidapi.com/query?function=GLOBAL_QUOTE&symbol="+s+"&datatype=json"

	req, _ := http.NewRequest("GET", api, nil);

	req.Header.Add("X-RapidAPI-Host", "alpha-vantage.p.rapidapi.com")
	req.Header.Add("X-RapidAPI-Key", "Here put your RapidAPI Key")

	res, err := http.DefaultClient.Do(req)

	if err != nil {
		fmt.Println("No response from request")
	}

	defer res.Body.Close()

	body, _ := ioutil.ReadAll(res.Body)

	
	if err := json.Unmarshal(body, &stock); err != nil {
		fmt.Println("Can not unmarshal JSON")
	}
	ch<- stock
	return stock
}
```
This function takes two parameters, a string which is just a stock symbol and a channel of Stock struct type. It then retrieves stock data of that stock symbol from the Alpha Vantage API.
> **Note**: Make sure to add your RapidAPI Key where indicated.

Copy the key indicated in your side of the screen below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akds161h4iogb5fzu9c7.png)
And paste it here:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xewpf3op0qk2r5ub1gwo.png)
Now that you have pasted your Rapid API key to the appropriate place, let's carry on with this function explanation. In two sequences we did the following:

**Sequence 1**: 
The function makes a GET request to the Alpha Vantage API using http package, it adds two header parameters to the request, which carries credentials containing the authentication information of our client for the stock data being requested. `X-RapidAPI-Host` and `X-RapidAPI-Key`.

**Sequence 2**:
The function reads the response body from the Alpha Vantage API with the `io/ioutil` package, converts the body from json of []byte to stock struct type with `json.unmarshal` and return the converted stock data via the channel to main thread `ch<- stock`.

**4. Created the genTable() function**:
```go
func genTable(watchList []Quote){
	tbl := table.New("SYMBOL", "OPEN", "HIGH", "LOW", "PRICE", "VOLUME", "PREVIOUS-CLOSE", "CHANGE")

	for _, stock := range watchList {
		tbl.AddRow(stock.SYMBOL, stock.OPEN, stock.HIGH, stock.LOW, stock.PRICE, stock.VOLUME, stock.PREVIOUS_CLOSE, stock.CHANGE)
	}

	tbl.Print()

}
```
genTable() takes an array of Quote struct type as parameter and generates a table from it. It will generate a row for every item in the array.

In the same folder `/stock-list`, add a go file named `main.go` and put the following code:
```go
package main

import (
	"strings"

	"github.com/gin-gonic/gin"
)

func main() {

	var watchList []Quote
	ch := make(chan Stock)
	
	router := gin.Default()

	router.GET("/stocks/", func (c *gin.Context){
		stockSymbol := c.Query("symbol")
		listOfSymbol := strings.Split(stockSymbol, ",")

		for _, symbol := range listOfSymbol{
			go getStock(symbol, ch)
		}

		for i := 0; i < len(listOfSymbol); i++ {
			result := <-ch

			watchList = append(watchList, Quote{
				SYMBOL: result.DETAIL.SYMBOL,
				OPEN: result.DETAIL.OPEN, 
				HIGH: result.DETAIL.HIGH, 
				LOW: result.DETAIL.LOW, 
				PRICE: result.DETAIL.PRICE, 
				VOLUME: result.DETAIL.VOLUME, 
				LATEST_TRADING_DAY: result.DETAIL.LATEST_TRADING_DAY, 
				PREVIOUS_CLOSE: result.DETAIL.PREVIOUS_CLOSE, 
				CHANGE: result.DETAIL.CHANGE, 
				CHANGE_PERCENT: result.DETAIL.CHANGE_PERCENT,
			})

		}


		genTable(watchList)
	})

	router.Run("localhost:8000")

}
```
###1.2 Code overview 2

Let's break down the code above;
**1. Imported packages**:
```go
package main

import (
	"strings"

	"github.com/gin-gonic/gin"
)
```
In this part of the code, `strings` package is imported to override string methods and `github.com/gin-gonic/gin` or Gin, a HTTP web framework to handle routing with simplicity.

**2. main() function**:
```go
func main() {

	var watchList []Quote
	ch := make(chan Stock)
	
	router := gin.Default()

	router.GET("/stocks/", func (c *gin.Context){
		stockSymbol := c.Query("symbol")
		listOfSymbol := strings.Split(stockSymbol, ",")

		for _, symbol := range listOfSymbol{
			go getStock(symbol, ch)
		}

		for i := 0; i < len(listOfSymbol); i++ {
			result := <-ch

			watchList = append(watchList, Quote{
				SYMBOL: result.DETAIL.SYMBOL,
				OPEN: result.DETAIL.OPEN, 
				HIGH: result.DETAIL.HIGH, 
				LOW: result.DETAIL.LOW, 
				PRICE: result.DETAIL.PRICE, 
				VOLUME: result.DETAIL.VOLUME, 
				LATEST_TRADING_DAY: result.DETAIL.LATEST_TRADING_DAY, 
				PREVIOUS_CLOSE: result.DETAIL.PREVIOUS_CLOSE, 
				CHANGE: result.DETAIL.CHANGE, 
				CHANGE_PERCENT: result.DETAIL.CHANGE_PERCENT,
			})

		}


		genTable(watchList)
	})

	router.Run()

}
```
This is the entry point of the executable program. In there, we declared an array `watchList` of struct Quote type to which stock data will be appended. Next, we created a channel with `ch := make(chan Stock)` to make the communication between main thread and child threads.

`router := gin.Default()`, creates gin router which is used to make a handler with `router.GET("/stocks/", HandlerFunction(){})`

**3. Handlerfunction(c *gin.Context)**:
This handler takes the query parameter with `		stockSymbol := c.Query("symbol")` using the Gin package and since the query might contain more than one string parameter separated by a comma, we derived an array of strings out of it with `listOfSymbol := strings.Split(stockSymbol, ",")`.

Then for each parameter(stock symbol), we concurrently make GET request to the Alpha Vantage API with:
```go
for _, symbol := range listOfSymbol{
			go getStock(symbol, ch)
		}
```

Next for every GET request, Alpha Vantage sends back a response of stock data that `go getStock(symbol, ch)` as a child thread, passes through the channel to the main thread. In the main thread, we store stock data in the `watchList` array and generate a table with `genTable(watchList)`.

**4. Finally, we start the router using `router.Run()`**

Our project directory should look like the project tree below:
```bash
.
├── go.mod
├── go.sum
├── main.go
└── stock.go

0 directories, 4 files
```

##2. Dockerize Go App

In this part of the tutorial, we will dockerize the Go application newly built. To do so, stay in the working directory, create a file named `Dockerfile` where you put the code below:
```Dockerfil
FROM golang:1.18-alpine

WORKDIR /app

COPY go.mod ./
COPY go.sum ./

RUN go mod download


COPY *.go ./

RUN go build -o /stock-list

CMD [ "/stock-list" ]
```
`FROM golang:1.18-alpine` this line tells Docker to use goland:1.18-alpine as the base image of our Go app image. It comes with all tools and packages to compile and run our application.

`WORKDIR /app` this one will create a working direction inside the image that we are building.

`COPY go.mod ./` and `COPY go.sum ./` instructions copy go.mod and go.sum files to the working direction in our docker image `/app`. 

`RUN go mod download` downloads our application dependencies inside the docker image working directory.

`COPY *.go ./` this instruction copies every go file in the our Go app local machine directory to the working directory in the docker image.

`RUN go build -o /stock-list` this will compile our application and `CMD [ "/stock-list" ]` will execute our compiled code.
 

###2.1 Build and Run docker image
After successfully creating the Dockerfile, let's build a docker image from it.

> Note: Before doing that, make sure to have a [Docker Hub account](https://hub.docker.com/) and with its credentials, sign in using `docker login` command in your local machine. 

In the project directory `/stock-list`, use your Docker Hub ID in the command below to build the image:
```bash
$ docker build --tag <YOUR DOCKER HUB ID>/stock-list .
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3wbf9k31wah1x8vhu4b.png)

**Visualize stock-list docker image by running this command**:
```bash
$ docker image ls
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l8j2lqbp4resn2v84igu.png)

**Run the stock-list image**:
```bash
$ docker run -p 8080:8080 <YOUR DOCKER HUB ID>/stock-list
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbr8fws189apa03p7ymr.png)
In order to view our running image or container, let run the command below:
```bash
$ docker container ls
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/538mo4oqsle20vlwq9oa.png)
After running this command, we can see that the docker container of stock-list image is exposed to the 8080 on your local machine `0.0.0.0:8080->8080/tcp`.

To see if the Go application is working correctly, we are going to make a GET request to the server using curl:
```bash
$ curl http://localhost:8080/stocks/?symbol=TSLA,TWTR,AAPL
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lqqf4ogeclb5vdfem5t1.png)

Find more stock symbols [here](https://stockanalysis.com/stocks/), to perform more test.

In addition, we can use different other commands to either stop the container in our case, the container running the stock-list image, restart or remove it.

To stop the container, let's take our container ID or container's name here; 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7yeed2qfox0q09ccp3jp.png)
Paste it to the command below:
```bash
$ docker stop <YOUR CONTAINER ID>
```
To restart, we can use the same container ID or container's name with this command:
```bash
$ docker restart <YOUR CONTAINER ID>
```
And to completely remove the container of stock-list image, still with the same container ID, let us use the following command:
```bash
$ docker rm <YOUR CONTAINER ID>
```
For more detailed docker commands, please check out the [official documentation](https://docs.docker.com/engine/reference/commandline/ps/).

###2.2 Deploy to Docker Hub
This is the last part of the tutorial where we will push our local stock-list image to Docker Hub. We successfully built our Go app and packed it with all necessary dependencies in a docker image; it is time to upload it to Docker Hub.  

Let's push our image with this command:
```bash
$ docker push <YOUR DOCKER HUB ID>/stock-list
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nw043btww5tbt37go8pe.png)
Doing this will make our stock-list image portable and accessible to other developers who can run it on their operating systems.

Our tutorial ends here guys, we built a Go application from scratch, we used some external go packages and made HTTP calls to a great API. To dockerize the application, we built a docker image from it, we ran it and pushed it to Docker Hub. Thanks for doing this together.

Find the project code [here](https://github.com/musole-masu/stock-data-list).