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)
}