Building API with Go


At this article we’ll try to build Golang based API.

Let’s create simple user auth service with sport events oriented service.

We will use github.com/jinzhu/gorm and github.com/dgrijalva/jwt-go as our main packages.

Main structs

Let’s describe two structs Token and User, we’ll use them for our token implementation and user model description.

Also, keep in mind gorm related things, like gorm.Model (ID alreacd) and auto migration of DB schema - super cool feature!

package models

import (
	"os"
	"strings"

	"github.com/dgrijalva/jwt-go"
	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
	u "maratgaliev.com/sporteefe/utils"
)

type Token struct {
	UserId uint
	jwt.StandardClaims
}

type User struct {
	gorm.Model
	Email    string `json:"email"`
	Password string `json:"password"`
	Token    string `json:"token";sql:"-"`
}

func (user *User) Validate() (map[string]interface{}, bool) {

	if !strings.Contains(user.Email, "@") {
		return u.Message(false, "Email address is required"), false
	}

	if len(user.Password) < 6 {
		return u.Message(false, "Password is required"), false
	}

	temp := &User{}

	err := GetDB().Table("users").Where("email = ?", user.Email).First(temp).Error
	if err != nil && err != gorm.ErrRecordNotFound {
		return u.Message(false, "Connection error. Please retry"), false
	}
	if temp.Email != "" {
		return u.Message(false, "Email address already in use"), false
	}

	return u.Message(false, "Requirement passed"), true
}

Basic actions

Let’s move forward with our business logic. Here we added Create(), Login(), GetUser() methods.

We creating new JWT token with NewWithClaims method and our signature and just return our data as response, also we use bcrypt here to hash our password.

At Login method we checking for password and comparing it with hash as usual, and generating JWT token at next step.

Helper method GetUser can be used in other methods to load User into our Struct.


func (user *User) Create() map[string]interface{} {

	if resp, ok := user.Validate(); !ok {
		return resp
	}

	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
	user.Password = string(hashedPassword)

	GetDB().Create(user)

	if user.ID <= 0 {
		return u.Message(false, "Failed to create user, connection error.")
	}

	tk := &Token{UserId: user.ID}
	token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
	tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
	user.Token = tokenString

	user.Password = ""

	response := u.Message(true, "User has been created")
	response["user"] = user
	return response
}

func Login(email, password string) map[string]interface{} {

	user := &User{}
	err := GetDB().Table("users").Where("email = ?", email).First(user).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return u.Message(false, "Email address not found")
		}
		return u.Message(false, "Connection error. Please retry")
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
		return u.Message(false, "Invalid login credentials. Please try again")
	}
	user.Password = ""

	tk := &Token{UserId: user.ID}
	token := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tk)
	tokenString, _ := token.SignedString([]byte(os.Getenv("token_password")))
	user.Token = tokenString

	resp := u.Message(true, "Logged In")
	resp["user"] = user
	return resp
}

func GetUser(u uint) *User {

	acc := &User{}
	GetDB().Table("users").Where("id = ?", u).First(acc)
	if acc.Email == "" {
		return nil
	}

	acc.Password = ""
	return acc
}

DB Connection and GORM

At this step we will use godotenv package to read environment variables like database settings etc

We also can turn on logging for DB queries and structs list for auto migrate feature.

package models

import (
	"fmt"
	"os"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"github.com/joho/godotenv"
)

var db *gorm.DB

func init() {

	e := godotenv.Load()
	if e != nil {
		fmt.Print(e)
	}

	username := os.Getenv("db_user")
	password := os.Getenv("db_pass")
	dbName := os.Getenv("db_name")
	dbHost := os.Getenv("db_host")

	dbUri := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s", dbHost, username, dbName, password)
	fmt.Println(dbUri)

	conn, err := gorm.Open("postgres", dbUri)
	if err != nil {
		fmt.Print(err)
	}

	db = conn
	db.LogMode(true)
	db.Debug().AutoMigrate(&User{}, &Contact{}, &Team{}, &Game{}, &Season{}, &Tournament{}, &Roster{})
}

func GetDB() *gorm.DB {
	return db
}

Additional commands

Ok, cool lets move forward and check other actions.

We have CreateUser and Authenticate here. These two actions can be moved to commands package and used separately.

Logic same - we are getting request data, and send respond with status and result of action execution.


var CreateUser = func(w http.ResponseWriter, r *http.Request) {

	user := &models.User{}
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		u.Respond(w, u.Message(false, "Invalid request"))
		return
	}

	resp := user.Create()
	if resp["status"] == false {
		w.WriteHeader(http.StatusBadRequest)
		u.Respond(w, u.Message(false, resp["message"].(string)))
		return
	}
	u.Respond(w, resp)
}

var Authenticate = func(w http.ResponseWriter, r *http.Request) {

	user := &models.User{}
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		u.Respond(w, u.Message(false, "Invalid request"))
		return
	}

	resp := models.Login(user.Email, user.Password)

	if resp["status"] == false {
		w.WriteHeader(http.StatusBadRequest)
		u.Respond(w, u.Message(false, resp["message"].(string)))
		return
	}

	u.Respond(w, resp)
}