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...
We are going to be creating a sign up end-point. For this, we begin by...
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
Oh, wait, we didn't configure the db connection yet.
$ createdb temp-db
package db
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
model "github.com/algogrit/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.
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...
package api
import (
"encoding/json"
"net/http"
db "github.com/algogrit/go-example/src/config/db"
model "github.com/algogrit/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...
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)
}
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.
nil
unless the type is a pointerSigning off for now. Please leave your thoughts and comments in the section below.