Rest in Go with Blue wheel, Uuuh

Sakib Sami
LiveKlass
Published in
6 min readOct 11, 2017

Go / Golang is a programming language developed by Google. Lets see how easy its to develop a micro service in Go. In the tutorial I will cover development to deployment of a micro service. For deployment I will use docker. To store data I will use sqlite for simplicity.

I will develop a contact management service. Where there will be users and user will have contacts.

So there are three models need to work with,

type User struct {
UserId int `json:"user_id"`
UserName string `json:"user_name"`
UserPassword string `json:"user_password"`
}
type Contact struct {
ContactId int `json:"contact_id"`
UserId int `json:"-"`
ContactName string `json:"contact_name"`
ContactNumber string `json:"contact_number"`
}

json:"-" means this field will be omitted when it will be converted to json by converter.

And,

type Session struct {
SessionId int `json:"session_id,omitempty"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
UserId int `json:"user_id,omitempty"`
}

json:"omitempty" means this field will be omitted when it will be converted to json by converter if its empty.

Note the json tag at right side. I will use go's built-In json converter to convert structure to json object and that time converter will use this json tag as key in json.

I need two package api to keep request handlers and data to keep data models to interact with database.

Will use go-sqlite3 as sqlite driver.
I need another thing called multiplexer to route requests to handlers. For that will use gorilla/mux. Thats all.

Lets start writing apis,

import (
"github.com/gorilla/mux"
"net/http"
"rest-in-go/api"
"fmt"
)
var routes = mux.NewRouter()// Route Configuration
func initRoutes() {
// Parent Routes
routes.HandleFunc("/", api.IndexHandler).Methods("GET", "POST", "PATCH", "OPTIONS", "DELETE", "PUT")
// Sub-routes { /api/v1 }
v1 := routes.NewRoute().PathPrefix("/api/v1").Subrouter()
v1.HandleFunc("/user/create", api.CreateUser).Methods("POST")
fmt.Println("Application Running On :8009...")
http.ListenAndServe(":8009", routes)
}
// Application Start Point
func main() {
initRoutes()
}

Its the start point of the application. routes is the type of Router. routes is the parent route And then added another sub-route named v1 and added PathPrefix as /api/v1, so parent url will be http://localhost:8009 and sub-route like http://localhost:8009/api/v1/..... and the server is listening on port 8009. Route configurations will be in initRoutes function.
Lets configure our first route which will be CreateUser.

// Basic structure of CreateUser function
// Will add other stuffs within the function
func CreateUser(w http.ResponseWriter, r *http.Request) {
}func initRoutes() {
routes.HandleFunc("/user/create", api.CreateUser).Methods("POST") // CreateUser
http.ListenAndServe(":8009", routes)
}

Now, lets move to database part as to go further have to store data and for that need database ready.

import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func NewConnection() *sql.DB {
db, err := sql.Open("sqlite3", "rest-api.db")
if err != nil {
panic(err)
}
return db
}
func GetRows(query string) (*sql.Rows, error) {
return NewConnection().Query(query)
}
func Exec(query string) bool {
_, err := NewConnection().Exec(query)
if err != nil {
return false
}
return true
}

Have a look on above example. On 3rd line imported driver for sqlite3 _ "github.com/mattn/go-sqlite3". As just imported the driver not using in code so compiler will complain about it as we can't keep unused imports in go. So put an _ before imported package, now compiler will ignore it.
with sql.Open("sqlite3", "rest-api.db") opened a database connection to file rest-api.db. There are two other function one is GetRows to execute a sql query and return result and other one is Exec just to execute queries. That it.

Have written a function attached to User structure to save user info in DB. Which is,

func (u *User) Save() bool {
query := "CREATE TABLE IF NOT EXISTS users(user_id INTEGER PRIMARY KEY AUTOINCREMENT, user_name TEXT, user_password TEXT);"
res := db.Exec(query)
if res {
query = "INSERT INTO users(user_name, user_password) VALUES('%s', '%s')"
query = fmt.Sprintf(query, u.UserName, u.UserPassword)
res = db.Exec(query)
return res
}
return false
}

Now will use it from handler to save User data.
Below is the complete code to handle CreateUser request,

func CreateUser(w http.ResponseWriter, r *http.Request) {
user := data.User{}
err := json.NewDecoder(r.Body).Decode(&user)
if err == nil {
if user.UserName != "" && user.UserPassword != "" {
if user.Save() {
json.NewEncoder(w).Encode(Response{
Code: http.StatusOK,
Message: "User successfully created.",
})
return
} else {
json.NewEncoder(w).Encode(Response{
Code: http.StatusInternalServerError,
Message: "Something went wrong.",
})
return
}
}
}
json.NewEncoder(w).Encode(Response{
Code: http.StatusBadRequest,
Message: "Malformed data received.",
})
}

At line 2 I have parsed the data from request body as json and holding them in User. Later pushed the User data to database using Save() function.

Request Format

POST /user/create

{
"user_name": "Sakib",
"user_password": "12345"
}

This is a example how you can handle POST request in go.

I won’t cover everything in the tutorial, because that will make the tutorial lengthy.
I will show another example that how you can handle GET request & implementation of Authorization. Then will moved to Deployment. But if you want to go through everything check the Source code.

Ok, Lets say now will implement CreateContact request.
As api will create contact against a user, so need to verify user first. If the user pass the authorization then there will be an contact entry against that user. So how can we implement that ?

Have to write a function like below. Function will have parameter of type http.HandlerFunc and will return http.HandlerFunc too. If the user authorized then will forward the request to route handler function by h.ServeHTTP(w, r), h is type of http.HandlerFunc.

func Authorization(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userId, err := strconv.Atoi(r.Header.Get("user_id"))
if err == nil {
accessToken := r.Header.Get("access_token")
if userId > 0 && accessToken != "" {
session := data.GetSession(accessToken, userId)
if session.AccessToken == accessToken && session.UserId == userId {
h.ServeHTTP(w, r)
return
}
}
}
json.NewEncoder(w).Encode(Response{
Code: http.StatusUnauthorized,
Message: "Unauthorized request.",
})
return
}
}

Within above code parsed access_token and user_id from Header.

Request format

{
"contact_name": "Sami",
"contact_number": "01710339938"
}

Have to pass handler function as parameter of authorization function.
In below code passed CreateContact as Authorization's parameter.

v1.HandleFunc("/contact/create", api.Authorization(api.CreateContact)).Methods("POST")

Contact create response format

{
"code": 401,
"message": "Unauthorized request."
}

Or

{
"code": 200,
"message": "Contact successfully created."
}

Uses gouuid to generate UUID.

Dockerize the Application

# Dockerfile
FROM golang
COPY . /go/src/rest-in-go
WORKDIR /go/src/rest-in-go
RUN go get .
ENTRYPOINT go run main.go
EXPOSE 8009

Its the Dockerfile content to make docker image with our app.

  • FROM golang : get the ubuntu image from dockerhub with go toolchain.
  • COPY . /go/src/rest-in-go : Copying source code to docker container at location /go/src/rest-in-go.
  • WORKDIR /go/src/rest-in-go : Setting working directory of docker image to /go/src/rest-in-go.
  • RUN go get . : Getting dependencies of the app.
  • ENTRYPOINT go run main.go : This line will be executed everytime you run the docker image.
  • EXPOSE 8009 : As application is listening on port 8009, so exposing port 8009 of docker container to host machine.

Finally execute docker build . -t sakibsami/rest-in-go and it will create the docker image with your app. After successfully creating the image you will get something like below image.

Check the created image by docker images

Finally run the application by,

docker run -p 8009:8009 sakibsami/rest-in-go:latest

You will get the application up & running. ;)

Source Code

Originally published at www.sakib.ninja on October 11, 2017.

--

--

Sakib Sami
LiveKlass

Senior Software Engineer @ Twilio | Entrepreneur | Tech Ninja