Build and Deploy a secure REST API with Go, Postgresql, JWT and GORM

In this tutorial, we are going to learn how to develop and deploy a secure REST api using Go Programming language.

Why Go?

Go is a very interesting programming language, it is a strongly typed language which compiles very fast, it performance is likened to that of C++, go has goroutines — a much more efficient replacement for Threads, and also go give you the freedom to static type on the web — I understand this is not new, i just love Go’s way.

What are we building?

We are going to build a **contact/phonebook manager App, **our API will allow users to add contacts to their profiles, they will be able to retrieve it in case their phone got lost.

Prerequisites

This lesson assumed you already installed the following packages

I also assumed you have setup your GOPATH. Check this if you haven’t https://github.com/golang/go/wiki/SettingGOPATH

Let’s do it!

What is REST?

REST stands for Representational State Transfer, it is the mechanism used by modern client apps to communicate with databases and servers via http — https://en.wikipedia.org/wiki/Representational_state_transfer So, you have a new startup idea or you want to build that awesome side project? REST protocol is mostly the way to go.

Building the App

We start by identifying the package dependencies we are going to need for this project, luckily for us, Go standard library is rich enough to build a complete website without using a third party framework(i hope i am right) — see net.http package, but to make our work easier, we are going to need the following packages,

To install any of this package, open terminal and run

go get github.com/{package-name}

This command will install the packages into your GOPATH.

Project Structure

Check the left sidebar to see project structureCheck the left sidebar to see project structure

utils.go

 1package utils
 2
 3import (
 4	"encoding/json"
 5	"net/http"
 6)
 7
 8func Message(status bool, message string) (map[string]interface{}) {
 9	return map[string]interface{} {"status" : status, "message" : message}
10}
11
12func Respond(w http.ResponseWriter, data map[string] interface{})  {
13	w.Header().Add("Content-Type", "application/json")
14	json.NewEncoder(w).Encode(data)
15}

utils.go contain handy utils functions to build json messages and return a json response. Note the two function Message() and Respond() before we proceed.

More about JWT

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. It is easy to identify web application users through sessions, however, when your web apps API is interacting with say an Android or IOS client, sessions becomes unusable because of the stateless nature of the http request. With JWT, we can create a unique token for each authenticated user, this token would be included in the header of the subsequent request made to the API server, this method allow us to identify every users that make calls to our API. Lets see the implementation below

 1package app
 2
 3import (
 4	"net/http"
 5	u "lens/utils"
 6	"strings"
 7	"go-contacts/models"
 8	jwt "github.com/dgrijalva/jwt-go"
 9	"os"
10	"context"
11	"fmt"
12)
13
14var JwtAuthentication = func(next http.Handler) http.Handler {
15
16	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
18		notAuth := []string{"/api/user/new", "/api/user/login"} //List of endpoints that doesn't require auth
19		requestPath := r.URL.Path //current request path
20
21		//check if request does not need authentication, serve the request if it doesn't need it
22		for _, value := range notAuth {
23
24			if value == requestPath {
25				next.ServeHTTP(w, r)
26				return
27			}
28		}
29
30		response := make(map[string] interface{})
31		tokenHeader := r.Header.Get("Authorization") //Grab the token from the header
32
33		if tokenHeader == "" { //Token is missing, returns with error code 403 Unauthorized
34			response = u.Message(false, "Missing auth token")
35			w.WriteHeader(http.StatusForbidden)
36			w.Header().Add("Content-Type", "application/json")
37			u.Respond(w, response)
38			return
39		}
40
41		splitted := strings.Split(tokenHeader, " ") //The token normally comes in format `Bearer {token-body}`, we check if the retrieved token matched this requirement
42		if len(splitted) != 2 {
43			response = u.Message(false, "Invalid/Malformed auth token")
44			w.WriteHeader(http.StatusForbidden)
45			w.Header().Add("Content-Type", "application/json")
46			u.Respond(w, response)
47			return
48		}
49
50		tokenPart := splitted[1] //Grab the token part, what we are truly interested in
51		tk := &models.Token{}
52
53		token, err := jwt.ParseWithClaims(tokenPart, tk, func(token *jwt.Token) (interface{}, error) {
54			return []byte(os.Getenv("token_password")), nil
55		})
56
57		if err != nil { //Malformed token, returns with http code 403 as usual
58			response = u.Message(false, "Malformed authentication token")
59			w.WriteHeader(http.StatusForbidden)
60			w.Header().Add("Content-Type", "application/json")
61			u.Respond(w, response)
62			return
63		}
64
65		if !token.Valid { //Token is invalid, maybe not signed on this server
66			response = u.Message(false, "Token is not valid.")
67			w.WriteHeader(http.StatusForbidden)
68			w.Header().Add("Content-Type", "application/json")
69			u.Respond(w, response)
70			return
71		}
72
73		//Everything went well, proceed with the request and set the caller to the user retrieved from the parsed token
74		fmt.Sprintf("User %", tk.Username) //Useful for monitoring
75		ctx := context.WithValue(r.Context(), "user", tk.UserId)
76		r = r.WithContext(ctx)
77		next.ServeHTTP(w, r) //proceed in the middleware chain!
78	});
79}

The comments inside the code explain everything there is to know, but basically, the code create a Middleware to intercept every requests, check for the presence of an authentication token (JWT token), verify if it is authentic and valid, then send error back to the client if we detect any deficiency in the token or proceed to serving the request otherwise(if the token is valid), you’ll see later, how we can access the user that is interacting with our API from the request.

Building the user registration and login system

We want our users to be able to register and login before backing up/storing their contacts on our system. The first thing we will need to do is connect to our database, we use a .env file to store our database credentials, my .env looks like this

db_name = gocontacts
db_pass = **** //This is default to the current user's password on windows for postgresql
db_user = postgres
db_type = postgres
db_host = localhost
db_port = 5434
token_password = thisIsTheJwtSecretPassword //Do not commit to git!

Then, we can connect to the database using the following snippets

 1package models
 2
 3import (
 4	_ "github.com/jinzhu/gorm/dialects/postgres"
 5	"github.com/jinzhu/gorm"
 6	"os"
 7	"github.com/joho/godotenv"
 8	"fmt"
 9)
10
11var db *gorm.DB //database
12
13func init() {
14
15	e := godotenv.Load() //Load .env file
16	if e != nil {
17		fmt.Print(e)
18	}
19
20	username := os.Getenv("db_user")
21	password := os.Getenv("db_pass")
22	dbName := os.Getenv("db_name")
23	dbHost := os.Getenv("db_host")
24
25
26	dbUri := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", dbHost, username, dbName, password) //Build connection string
27	fmt.Println(dbUri)
28
29	conn, err := gorm.Open("postgres", dbUri)
30	if err != nil {
31		fmt.Print(err)
32	}
33
34	db = conn
35	db.Debug().AutoMigrate(&Account{}, &Contact{}) //Database migration
36}
37
38//returns a handle to the DB object
39func GetDB() *gorm.DB {
40	return db
41}

The code does a very simple thing, on the file init() function — init() automatically get called by Go, the code retrieve connection information from .env file then build a connection string and use it to connect to the database.

Creating the application entry point

So far, we’ve been able to create the JWT middleware and connect to our database. The next thing is creating the application’s entry point, see the code snippet below

 1package main
 2
 3import (
 4	"github.com/gorilla/mux"
 5	"go-contacts/app"
 6	"os"
 7	"fmt"
 8	"net/http"
 9)
10
11func main() {
12
13	router := mux.NewRouter()
14	router.Use(app.JwtAuthentication) //attach JWT auth middleware
15
16	port := os.Getenv("PORT") //Get port from .env file, we did not specify any port so this should return an empty string when tested locally
17	if port == "" {
18		port = "8000" //localhost
19	}
20
21	fmt.Println(port)
22
23	err := http.ListenAndServe(":" + port, router) //Launch the app, visit localhost:8000/api
24	if err != nil {
25		fmt.Print(err)
26	}
27}

We create a new Router object — line 13, we attach our JWT auth middleware using router’s Use() function — line 14, and then we proceed to start listening for incoming requests.

Use the small media play button located left of func main() to compile and launch the app, if all is good, you should see no error in the console, in case there was an error, take a second look at your database connection parameters to see that they correlate.

Results. DB migrations has occurred, GORM converted go struct to database tablesResults. DB migrations has occurred, GORM converted go struct to database tables

Creating and authenticating Users

create a new file models/accounts.go,

  1package models
  2
  3import (
  4	"github.com/dgrijalva/jwt-go"
  5	u "lens/utils"
  6	"strings"
  7	"github.com/jinzhu/gorm"
  8	"os"
  9	"golang.org/x/crypto/bcrypt"
 10)
 11
 12/*
 13JWT claims struct
 14*/
 15type Token struct {
 16	UserId uint
 17	jwt.StandardClaims
 18}
 19
 20//a struct to rep user account
 21type Account struct {
 22	gorm.Model
 23	Email string `json:"email"`
 24	Password string `json:"password"`
 25	Token string `json:"token";sql:"-"`
 26}
 27
 28//Validate incoming user details...
 29func (account *Account) Validate() (map[string] interface{}, bool) {
 30
 31	if !strings.Contains(account.Email, "@") {
 32		return u.Message(false, "Email address is required"), false
 33	}
 34
 35	if len(account.Password) < 6 {
 36		return u.Message(false, "Password is required"), false
 37	}
 38
 39	//Email must be unique
 40	temp := &Account{}
 41
 42	//check for errors and duplicate emails
 43	err := GetDB().Table("accounts").Where("email = ?", account.Email).First(temp).Error
 44	if err != nil && err != gorm.ErrRecordNotFound {
 45		return u.Message(false, "Connection error. Please retry"), false
 46	}
 47	if temp.Email != "" {
 48		return u.Message(false, "Email address already in use by another user."), false
 49	}
 50
 51	return u.Message(false, "Requirement passed"), true
 52}
 53
 54func (account *Account) Create() (map[string] interface{}) {
 55
 56	if resp, ok := account.Validate(); !ok {
 57		return resp
 58	}
 59
 60	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost)
 61	account.Password = string(hashedPassword)
 62
 63	GetDB().Create(account)
 64
 65	if account.ID <= 0 {
 66		return u.Message(false, "Failed to create account, connection error.")
 67	}
 68
 69	//Create new JWT token for the newly registered account
 70	tk := &Token{UserId: account.ID}
 71	token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
 72	tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
 73	account.Token = tokenString
 74
 75	account.Password = "" //delete password
 76
 77	response := u.Message(true, "Account has been created")
 78	response["account"] = account
 79	return response
 80}
 81
 82func Login(email, password string) (map[string]interface{}) {
 83
 84	account := &Account{}
 85	err := GetDB().Table("accounts").Where("email = ?", email).First(account).Error
 86	if err != nil {
 87		if err == gorm.ErrRecordNotFound {
 88			return u.Message(false, "Email address not found")
 89		}
 90		return u.Message(false, "Connection error. Please retry")
 91	}
 92
 93	err = bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(password))
 94	if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { //Password does not match!
 95		return u.Message(false, "Invalid login credentials. Please try again")
 96	}
 97	//Worked! Logged In
 98	account.Password = ""
 99
100	//Create JWT token
101	tk := &Token{UserId: account.ID}
102	token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
103	tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
104	account.Token = tokenString //Store the token in the response
105
106	resp := u.Message(true, "Logged In")
107	resp["account"] = account
108	return resp
109}
110
111func GetUser(u uint) *Account {
112
113	acc := &Account{}
114	GetDB().Table("accounts").Where("id = ?", u).First(acc)
115	if acc.Email == "" { //User not found!
116		return nil
117	}
118
119	acc.Password = ""
120	return acc
121}

There is a lot of puzzle inside accounts.go, lets break it down a little bit.

The first part create two structs Token and Account they represent a JWT token claim and a user account respectively. Function Validate() validates the data sent from clients and function Create() creates a new account and generate a JWT token that will be sent back to client that made the request. Function Login(username, password) authenticate an existing user, then generate a JWT token if authentication was successful.

authController.go

 1package controllers
 2
 3import (
 4	"net/http"
 5	u "go-contacts/utils"
 6	"go-contacts/models"
 7	"encoding/json"
 8)
 9
10var CreateAccount = func(w http.ResponseWriter, r *http.Request) {
11
12	account := &models.Account{}
13	err := json.NewDecoder(r.Body).Decode(account) //decode the request body into struct and failed if any error occur
14	if err != nil {
15		u.Respond(w, u.Message(false, "Invalid request"))
16		return
17	}
18
19	resp := account.Create() //Create account
20	u.Respond(w, resp)
21}
22
23var Authenticate = func(w http.ResponseWriter, r *http.Request) {
24
25	account := &models.Account{}
26	err := json.NewDecoder(r.Body).Decode(account) //decode the request body into struct and failed if any error occur
27	if err != nil {
28		u.Respond(w, u.Message(false, "Invalid request"))
29		return
30	}
31
32	resp := models.Login(account.Email, account.Password)
33	u.Respond(w, resp)
34}

The content is very straightforward. It contains the handler for /user/new and /user/login endpoints.

Add the following snippet to main.go to register our new routes

router.HandleFunc(**"/api/user/new"**, controllers.CreateAccount).Methods(**"POST"**)

router.HandleFunc(**"/api/user/login"**, controllers.Authenticate).Methods(**"POST"**)

The above code register both /user/new and /user/login endpoints and pass their corresponding request handlers.

Now, recompile the code and visit localhost:8000/api/user/new using postman, set the request body to application/json as shown below

Response from /user/newResponse from /user/new

If you try to call /user/new twice with the same payload, you’ll receive a response that the email already exists, works according to our instructions.

Creating contacts

Part of our app’s functionality is letting our users create/store contacts. Contact will have name and phone , we will define these as struct properties. The following snippets belongs to models/contact.go

 1package models
 2
 3import (
 4	u "go-contacts/utils"
 5	"github.com/jinzhu/gorm"
 6	"fmt"
 7)
 8
 9type Contact struct {
10	gorm.Model
11	Name string `json:"name"`
12	Phone string `json:"phone"`
13	UserId uint `json:"user_id"` //The user that this contact belongs to
14}
15
16/*
17 This struct function validate the required parameters sent through the http request body
18returns message and true if the requirement is met
19*/
20func (contact *Contact) Validate() (map[string] interface{}, bool) {
21
22	if contact.Name == "" {
23		return u.Message(false, "Contact name should be on the payload"), false
24	}
25
26	if contact.Phone == "" {
27		return u.Message(false, "Phone number should be on the payload"), false
28	}
29
30	if contact.UserId <= 0 {
31		return u.Message(false, "User is not recognized"), false
32	}
33
34	//All the required parameters are present
35	return u.Message(true, "success"), true
36}
37
38func (contact *Contact) Create() (map[string] interface{}) {
39
40	if resp, ok := contact.Validate(); !ok {
41		return resp
42	}
43
44	GetDB().Create(contact)
45
46	resp := u.Message(true, "success")
47	resp["contact"] = contact
48	return resp
49}
50
51func GetContact(id uint) (*Contact) {
52
53	contact := &Contact{}
54	err := GetDB().Table("contacts").Where("id = ?", id).First(contact).Error
55	if err != nil {
56		return nil
57	}
58	return contact
59}
60
61func GetContacts(user uint) ([]*Contact) {
62
63	contacts := make([]*Contact, 0)
64	err := GetDB().Table("contacts").Where("user_id = ?", user).Find(&contacts).Error
65	if err != nil {
66		fmt.Println(err)
67		return nil
68	}
69
70	return contacts
71}

Same as in models/accounts.go we create a function Validate() to validate the passed inputs, we return an error with messages if anything we don’t want occur, then we wrote function Create() to insert this contact into the database.

The only part left is retrieving the contacts. Lets do it!

router.HandleFunc(**"/api/me/contacts"**, controllers.GetContactsFor).Methods(**"GET"**)

Add the above snippet to main.go to tell the router to register /me/contacts endpoint. Lets create controllers.GetContactsFor handler to handle the API request.

contactsController.go

Bellow is the content of contactsController.go

 1package controllers
 2
 3import (
 4	"net/http"
 5	"go-contacts/models"
 6	"encoding/json"
 7	u "go-contacts/utils"
 8	"strconv"
 9	"github.com/gorilla/mux"
10	"fmt"
11)
12
13var CreateContact = func(w http.ResponseWriter, r *http.Request) {
14
15	user := r.Context().Value("user") . (uint) //Grab the id of the user that send the request
16	contact := &models.Contact{}
17
18	err := json.NewDecoder(r.Body).Decode(contact)
19	if err != nil {
20		u.Respond(w, u.Message(false, "Error while decoding request body"))
21		return
22	}
23
24	contact.UserId = user
25	resp := contact.Create()
26	u.Respond(w, resp)
27}
28
29var GetContactsFor = func(w http.ResponseWriter, r *http.Request) {
30
31	params := mux.Vars(r)
32	id, err := strconv.Atoi(params["id"])
33	if err != nil {
34		//The passed path parameter is not an integer
35		u.Respond(w, u.Message(false, "There was an error in your request"))
36		return
37	}
38	
39	data := models.GetContacts(uint(id))
40	resp := u.Message(true, "success")
41	resp["data"] = data
42	u.Respond(w, resp)
43}

What it does is pretty similar to authController.go’s , but basically, it grabs the json body and decode it into Contact struct, if there was an error, return a response immediately or insert the contacts into the database if everything went well.

Fetching Contacts that belongs to a user

Now, our users have been able to store their contacts successfully, what if they want to retrieve the contact they stored, in case their phone is lost? Visiting /me/contacts should return a json structure for the contacts of the API caller(current user). Check the code snippet to have a clearer picture.

Normally, retrieving user’s contacts endpoint should look like /user/{userId}/contacts , specifying userId as a path parameter is very dangerous, because every authenticated user can craft a request to this path and contacts of another users would be returned without any problem, this can lead to a brutal attack by hackers — I am trying to point out the usefulness of JWT . We can easily obtain the id of the API caller using r.Context().Value(“user”) , remember we set this value inside auth.go — Our authentication middleware

 1package controllers
 2
 3import (
 4	"net/http"
 5	"go-contacts/models"
 6	"encoding/json"
 7	u "go-contacts/utils"
 8	"strconv"
 9	"github.com/gorilla/mux"
10	"fmt"
11)
12
13var CreateContact = func(w http.ResponseWriter, r *http.Request) {
14
15	user := r.Context().Value("user") . (uint) //Grab the id of the user that send the request
16	contact := &models.Contact{}
17
18	err := json.NewDecoder(r.Body).Decode(contact)
19	if err != nil {
20		u.Respond(w, u.Message(false, "Error while decoding request body"))
21		return
22	}
23
24	contact.UserId = user
25	resp := contact.Create()
26	u.Respond(w, resp)
27}
28
29var GetContactsFor = func(w http.ResponseWriter, r *http.Request) {
30
31	params := mux.Vars(r)
32	id, err := strconv.Atoi(params["id"])
33	if err != nil {
34		//The passed path parameter is not an integer
35		u.Respond(w, u.Message(false, "There was an error in your request"))
36		return
37	}
38	
39	data := models.GetContacts(uint(id))
40	resp := u.Message(true, "success")
41	resp["data"] = data
42	u.Respond(w, resp)
43}

Response for /me/contactsResponse for /me/contacts

The code for this project is on github — https://github.com/adigunhammedolalekan/go-contacts

Deployment

We can easily deploy our app to heroku. Firstly, download godep . godep is a dependency manager for Golang, similar to npm for nodejs.

go get -u github.com/tools/godep

If everything went well, your screen should look like my own.

Voila! Your app has been deployed. The next thing is setting up a remote Postgresql database.

run heroku addons:create heroku-postgresql:hobby-dev to create the database. To learn more about this, visit https://devcenter.heroku.com/articles/heroku-postgresql

Great! We are almost there, next thing is to connect with our remote database.

Go to heroku.com and login with your credentials, you should find your newly created app on your dashboard, click on it. After that, click on settings, then click on Reveal Config Vars Postgresql connection URI format postgres://username:password@host/dbName , There is a var named DATABASE_URL , this was automatically added to your .env file when you created the postgresql database (Note: Heroku automatically replace your local .env when you deploy your app), from this var, we will extract our database connection parameter.

I extracted database connection parameter from the auto generated DATABASE_URL varsI extracted database connection parameter from the auto generated DATABASE_URL vars

If all this went well, your API should be live now!

As you can see, the api is live!As you can see, the api is live!

I tried my best to make this lesson clear as much as possible. Please, bear with me for any error you might encountered. I am just trying to share my knowledge.

Project Repo — https://github.com/adigunhammedolalekan/go-contacts

If you have any question, or a correction, i will be glad to know.

Follow me on Twitter — www.twitter.com/L3kanAdigun

Hire me(I am available and actively looking for job, DM me on Twitter) —www.twitter.com/L3kanAdigun

You can mail me personally — adigunhammed.lekan@gmail.com

Its really a long article, Thanks for reading.