Blog Project with Go, Gin, MySQL and Docker - Part 3

Blog Project with Go, Gin, MySQL and Docker - Part 3

We have left off part 2 by creating the blog apis. In this article we will design the User struct and implement User authentication. We will be using JWT token for authentication.

Do make sure you have gone through Part 2 and Part 1.

Note : All codes for this series can be found on this repo. Please checkout to branch
part-3 by git checkout part-3 to get code for this article.

contents

Create user apis and authentication

Designing user struct

Inside models folder add a user.go file with below contents.

package models
import "time"

//User -> User struct to save user on database
type User struct {
    ID        int64     `gorm:primary_key;auto_increment;json:id`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Email     string    `json:"email"`
    Password  string    `json:"password"`
    IsActive  bool      `json:"is_active"`
    CreatedAt time.Time `json:"created_at", omitempty`
    UpdatedAt time.Time `json:"updated_at", omitempty`
}

//TableName -> returns the table name of User Model
func (user *User) TableName() string {
    return "user"
}

//UserLogin -> Request Binding for User Login
type UserLogin struct {
    Email    string `form:"email" binding:"required"`
    Password string `form:"password" binding:"required"`
}

//UserRegister -> Request Binding for User Register
type UserRegister struct {
    Email     string `form:"email" json:"email" binding:"required"`
    Password  string `form:"password" json:"password" binding:"required"`
    FirstName string `form:"first_name"`
    LastName  string `form:"last_name"`
}

//ResponseMap -> response map method of User
func (user *User) ResponseMap() map[string]interface{} {
    resp := make(map[string]interface{})
    resp["id"] = user.ID
    resp["email"] = user.Email
    resp["first_name"] = user.FirstName
    resp["last_name"] = user.LastName
    resp["is_active"] = user.IsActive
    resp["created_at"] = user.CreatedAt
    resp["updated_at"] = user.UpdatedAt
    return resp
}

Hash and compare hashed password

For the security purpose we will encrypt hashed password on database. Execute
go get golang.org/x/crypto/bcrypt on the command line to install the bcrypt package. Create util/password.go file and add the following code.

package util
import "golang.org/x/crypto/bcrypt"

func HashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    return string(bytes), err
}

func CheckPasswordHash(password string, hash string) error {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err
}

User register and login repositories

Add the api/repository/user.go file and add the following code.

package repository

import (
    "blog/infrastructure"
    "blog/models"
    "blog/util"
)

//UserRepository -> UserRepository resposible for accessing database
type UserRepository struct {
    db infrastructure.Database
}

//NewUserRepository -> creates a instance on UserRepository
func NewUserRepository(db infrastructure.Database) UserRepository {
    return UserRepository{
        db: db,
    }
}

//CreateUser -> method for saving user to database
func (u UserRepository) CreateUser(user models.UserRegister) error {

    var dbUser models.User
    dbUser.Email = user.Email
    dbUser.FirstName = user.FirstName
    dbUser.LastName = user.LastName
    dbUser.Password = user.Password
    dbUser.IsActive = true
    return u.db.DB.Create(&dbUser).Error
}

//LoginUser -> method for returning user
func (u UserRepository) LoginUser(user models.UserLogin) (*models.User, error) {

    var dbUser models.User
    email := user.Email
    password := user.Password

    err := u.db.DB.Where("email = ?", email).First(&dbUser).Error
    if err != nil {
        return nil, err
    }

    hashErr := util.CheckPasswordHash(password, dbUser.Password)
    if hashErr != nil {
        return nil, hashErr
    }
    return &dbUser, nil
}

Here dbUser.IsActive = true this might seems a little bit off ? No, this is on purpose for now. Later on upcoming part we will implement User confirmation via email.

User register and login services

Add the api/service/user.go file and add the following code.

package service
import (
    "blog/api/repository"
    "blog/models"
)

//UserService UserService struct
type UserService struct {
    repo repository.UserRepository
}

//NewUserService : get injected user repo
func NewUserService(repo repository.UserRepository) UserService {
    return UserService{
        repo: repo,
    }
}

//Save -> saves users entity
func (u UserService) CreateUser(user models.UserRegister) error {
    return u.repo.CreateUser(user)
}

//Login -> Gets validated user
func (u UserService) LoginUser(user models.UserLogin) (*models.User, error) {
    return u.repo.LoginUser(user)

}

User register and login controllers

Install jwt package by go get github.com/golang-jwt/jwt this package will be used to issue JWT Token. Add the api/controller/user.go file and add the following.

package controller

import (
    "blog/api/service"
    "blog/models"
    "blog/util"
    "net/http"
    "time"

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

//UserController struct
type UserController struct {
    service service.UserService
}

//NewUserController : NewUserController
func NewUserController(s service.UserService) UserController {
    return UserController{
        service: s,
    }
}

//CreateUser ->  calls CreateUser services for validated user
func (u *UserController) CreateUser(c *gin.Context) {
    var user models.UserRegister
    if err := c.ShouldBind(&user); err != nil {
        util.ErrorJSON(c, http.StatusBadRequest, "Inavlid Json Provided")
        return
    }

    hashPassword, _ := util.HashPassword(user.Password)
    user.Password = hashPassword

    err := u.service.CreateUser(user)
    if err != nil {
        util.ErrorJSON(c, http.StatusBadRequest, "Failed to create user")
        return
    }

    util.SuccessJSON(c, http.StatusOK, "Successfully Created user")
}

//LoginUser : Generates JWT Token for validated user
func (u *UserController) Login(c *gin.Context) {
    var user models.UserLogin
    var hmacSampleSecret []byte
    if err := c.ShouldBindJSON(&user); err != nil {
        util.ErrorJSON(c, http.StatusBadRequest, "Inavlid Json Provided")
        return
    }
    dbUser, err := u.service.LoginUser(user)
    if err != nil {
        util.ErrorJSON(c, http.StatusBadRequest, "Invalid Login Credentials")
        return
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
        "user": dbUser,
        "exp":  time.Now().Add(time.Minute * 15).Unix(),
    })

    tokenString, err := token.SignedString(hmacSampleSecret)
    if err != nil {
        util.ErrorJSON(c, http.StatusBadRequest, "Failed to get token")
        return
    }
    response := &util.Response{
        Success: true,
        Message: "Token generated sucessfully",
        Data:    tokenString,
    }
    c.JSON(http.StatusOK, response)
}

User register and login routes

Finally, we have come to the part where we can expose the register and login apis. Add the following code to api/routes/user.go .

package routes

import (
    "blog/api/controller"
    "blog/infrastructure"
)

//UserRoute -> Route for user module
type UserRoute struct {
    Handler    infrastructure.GinRouter
    Controller controller.UserController
}

//NewUserRoute -> initializes new instance of UserRoute
func NewUserRoute(
    controller controller.UserController,
    handler infrastructure.GinRouter,
) UserRoute {
    return UserRoute{
        Handler:    handler,
        Controller: controller,
    }
}

//Setup -> setups user routes
func (u UserRoute) Setup() {
    user := u.Handler.Gin.Group("/auth")
    {
        user.POST("/register", u.Controller.CreateUser)
        user.POST("/login", u.Controller.LoginUser)
    }
}

The above code will configure user authentication routes register and login.

Configure main.go

Now, final piece of code is to update main.go file, gluing all things together.

package main
import (
    "blog/api/controller"
    "blog/api/repository"
    "blog/api/routes"
    "blog/api/service"
    "blog/infrastructure"
    "blog/models"
)

func init() {
    infrastructure.LoadEnv()
}

func main() {

    router := infrastructure.NewGinRouter()
    db := infrastructure.NewDatabase()

    postRepository := repository.NewPostRepository(db)
    postService := service.NewPostService(postRepository)
    postController := controller.NewPostController(postService)
    postRoute := routes.NewPostRoute(postController, router)
    postRoute.Setup()

    // add up these 
    userRepository := repository.NewUserRepository(db)
    userService := service.NewUserService(userRepository)
    userController := controller.NewUserController(userService)
    userRoute := routes.NewUserRoute(userController, router)
    userRoute.Setup()

    db.DB.AutoMigrate(&models.Post{}, &models.User{})

    router.Gin.Run(":8000")
}

API demo

It's time to spin up the server and test out register and login apis.

docker-compose up --build

Now, Bring up your favorite API Tester application. I will be using postman

Testing register endpoint -> /auth/register

Imgur

Testing login endpoint -> /auth/login

Imgur

verify token on jwt.io

Imgur

Wrap Up

Next up, Upcoming Part 4 will cover the following :

  • Adding user to blog
  • Apis of related user posts
  • and many more

Thanks a lot! A feedback is really appreciated. I am on Linkedin. Let's connect.

Happy reading !!!