Learn go series: Part II - Gorm up!

This is Part 2 of "Learn go" series. You can find the previous post here.

In this post, I will be walking you through an example of interacting with database. A very common scenario, creating a user and storing the password securely. This post picks up from the previous post, so if you haven't read, please skim through it.

Let's begin...

#Defining the problem

We are going to be creating a sign up end-point. For this, we begin by...

#Defining the type User

#user.go

package model

import "github.com/jinzhu/gorm"

type User struct {
  gorm.Model
  Username       string `gorm:"not null;unique"`
  HashedPassword string `gorm:"not null"`
  FirstName      string
  LastName       string
  MobileNumber   string `gorm:"not null;unique"`
}

I have defined password as 'HashedPassword'. We will be storing the password as one-way encrypted hash + salt in this field. If you aren't sure why you should salt and hash your password, please read this crypto.stackexchange.com post

#Setting up and configuring db

Oh, wait, we didn't configure the db connection yet.

#Creating postgres db

$ createdb temp-db

#db.go

package db

import (
  "github.com/jinzhu/gorm"
  _ "github.com/jinzhu/gorm/dialects/postgres"

  model "github.com/gauravagarwalr/go-example/src/models"
)

var dbInstance *gorm.DB

func InitializeDB() {
  dbName := "temp-db"

  localDb, err := gorm.Open("postgres", "dbname="+dbName+" sslmode=disable")

  if err != nil {
    panic("failed to connect database")
  }

  localDb.LogMode(goAppEnvironment != "production")
  dbInstance = localDb

  // Migrate the schema
  dbInstance.AutoMigrate(&model.User{})
}

func Instance() *gorm.DB {
  return dbInstance
}

In the InitializeDB function we are connecting to the postgres database and we are migrating the app. We will be calling this from the main function of our app.

#Tying it together - An API and crypted password

With the user model defined and the db configured. All we have left to do is the api. Let's see how simple it is to do...

#api.go

package api

import (
  "encoding/json"
  "net/http"

  db "github.com/gauravagarwalr/go-example/src/config/db"
  model "github.com/gauravagarwalr/go-example/src/models"
)

type newUser struct {
  Username     string
  FirstName    string
  LastName     string
  MobileNumber string
  Password     string
}

func CreateUserHandler(w http.ResponseWriter, req *http.Request) {
  var newUser newUser
  json.NewDecoder(req.Body).Decode(&newUser)

  user := model.User{
    Username:     newUser.Username,
    FirstName:    newUser.FirstName,
    LastName:     newUser.LastName,
    MobileNumber: newUser.MobileNumber}
  user.HashedPassword = model.HashAndSalt(newUser.Password)

  if err := db.Instance().Create(&user).Error; err != nil {
    http.Error(w, err.Error(), unprocessableEntity)
    return
  }

  json.NewEncoder(w).Encode(user)
}

func RunServer(port string) {
  router := mux.NewRouter()

  router.Handle("/users", CreateUserHandler).Methods("POST")

  log.Fatal(http.ListenAndServe(":"+port, router))
}

CreateUserHandler as the name suggests, gets the users details from the request body and parses it using a newUser struct. We then instantiate the user and save it to the database. In case of any validation issues it sends back a string explaining the error as response with 422 - Unprocessable Entity error code.

Pay close attention to user.HashedPassword = model.HashAndSalt(newUser.Password). We haven't defined the function yet. Let's go back to user.go in model package and define it...

#user.go

import {
  "log"
  "github.com/jinzhu/gorm"
  "golang.org/x/crypto/bcrypt"
}

func HashAndSalt(pwd string) string {
  hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.MinCost)
  if err != nil {
    log.Println(err)
  }

  return string(hash)
}

#Conclusion

We have built a simple way of storing new user information along with their password in a secure format. Gorm does a lot of things right. And for the sake keeping this post short, I haven't dived deep into some of its interesting aspects. You can see it in action in a sample api on Github.

#Caveats & Honorable mentions

Signing off for now. Please leave your thoughts and comments in the section below.




Latest Posts