Golang Course With Building a Fintech Banking App – Lesson 2: Login and REST API
It’s the second lesson of the golang course.
Intro to the Golang course Lesson 2
In the last episode of the Golang course, we did a project setup and the first database migration.
Here is the URL:
Golang course with building a fintech banking app – Lesson 1: Start the project
In the last episode of the “Learn Angular 9 with Tailwind CSS by building a fintech banking app”, my friend Anna created the project setup and the first Login UI.
You can use that to connect with our backend and be building the complete fintech app.
Here is the URL:
Angular Course with building a banking application with Tailwind CSS – Lesson 1: Start the project
In today’s episode, you will learn the second part of how Golang course, and we will focus on user authentication.
I will teach you how to build login functionality and how to create the first endpoints of the REST API.
And we will do a bit refactoring of the old code to make it cleaner.
Let’s start!
Btw, if you prefer video, here is the youtube version:
Refactor interfaces
The first step that we need to do is to refactor the interfaces that we created in the previous episode.
To do that, we need to create dir named “interfaces” and create the file called “interfaces.go” inside this directory.
package interfaces import "github.com/jinzhu/gorm" type User struct { gorm.Model Username string Email string Password string } type Account struct { gorm.Model Type string Name string Balance uint UserID uint } type ResponseAccount struct { ID uint Name string Balance int } type ResponseUser struct { ID uint Username string Email string Accounts []ResponseAccount }
Next, we need to use the structs from the interfaces.go file instead of these from the migrations.go.
We need to change them in places where we relate to the “User” and “Account” structs.
Your migrations.go file should look like the example below.
package migrations import ( "duomly.com/go-bank-backend/helpers" "duomly.com/go-bank-backend/interfaces" _ "github.com/jinzhu/gorm/dialects/postgres" ) func connectDB() *gorm.DB { db, err := gorm.Open("postgres", "host=127.0.0.1 port=5432 user=postgres dbname=bankapp password=postgres sslmode=disable") HandleErr(err) return db } func createAccounts() { db := connectDB() users := &[2]interfaces.User{ {Username: "Martin", Email: "martin@martin.com"}, {Username: "Michael", Email: "michael@michael.com"}, } for i := 0; i < len(users); i++ { // Correct one way generatedPassword := helpers.HashAndSalt([]byte(users[i].Username)) user := &interfaces.User{Username: users[i].Username, Email: users[i].Email, Password: generatedPassword} db.Create(&user) account := &interfaces.Account{Type: "Daily Account", Name: string(users[i].Username + "'s" + " account"), Balance: uint(10000 * int(i+1)), UserID: user.ID} db.Create(&account) } defer db.Close() } func Migrate() { User := &interfaces.User{} Account := &interfaces.Account{} db := connectDB() db.AutoMigrate(&User, &Account) defer db.Close() createAccounts() }
Refactor ConnectDB to helpers
The next step will also be related to the migrations.go file, and to the helpers.go.
We need to move the “connectDB” function to the helpers.go.
Next, we need to export it by naming as „ConnectDB”.
The last step will be to reuse it in migrations.go but in the imported form.
Your helpers.go should look like the example below:
package helpers import ( "github.com/jinzhu/gorm" _ "github.com/lib/pq" "golang.org/x/crypto/bcrypt" ) func HandleErr(err error) { if err != nil { panic(err.Error()) } } func HashAndSalt(pass []byte) string { hashed, err := bcrypt.GenerateFromPassword(pass, bcrypt.MinCost) HandleErr(err) return string(hashed) } func ConnectDB() *gorm.DB { db, err := gorm.Open("postgres", "host=127.0.0.1 port=5432 user=postgres dbname=bankapp password=postgres sslmode=disable") HandleErr(err) return db }
Your migrations.go should look like the example below:
package migrations import ( "duomly.com/go-bank-backend/helpers" "duomly.com/go-bank-backend/interfaces" _ "github.com/jinzhu/gorm/dialects/postgres" ) func createAccounts() { db := helpers.ConnectDB() users := &[2]interfaces.User{ {Username: "Martin", Email: "martin@martin.com"}, {Username: "Michael", Email: "michael@michael.com"}, } for i := 0; i < len(users); i++ { // Correct one way generatedPassword := helpers.HashAndSalt([]byte(users[i].Username)) user := &interfaces.User{Username: users[i].Username, Email: users[i].Email, Password: generatedPassword} db.Create(&user) account := &interfaces.Account{Type: "Daily Account", Name: string(users[i].Username + "'s" + " account"), Balance: uint(10000 * int(i+1)), UserID: user.ID} db.Create(&account) } defer db.Close() } func Migrate() { User := &interfaces.User{} Account := &interfaces.Account{} db := helpers.ConnectDB() db.AutoMigrate(&User, &Account) defer db.Close() createAccounts() }
Create a login function
Now we can go into the login logic.
The first action that we should do is to create a directory named “users” and a file named “users.go”.
In the “users.go” we should create a package named “users”, and the empty function called “Login”.
Function “login” should take “username” and “pass” as strings.
And we should be able to return a map with “any” type of keys.
You should import a few deps as well, but you can copy them from the example below.
package users import ( "time" "duomly.com/go-bank-backend/helpers" "duomly.com/go-bank-backend/interfaces" "github.com/dgrijalva/jwt-go" "golang.org/x/crypto/bcrypt" ) func Login(username string, pass string) map[string]interface{} { }
Connect DB in the login
The next important thing that we will do is to create the db connection and look for the user with a username from the function params.
We need to remember about the “if” statement with the RecordNotFound function that will notify us if the user has the status “not found”.
db := helpers.ConnectDB() user := &interfaces.User{} if db.Where("username = ? ", username).First(&user).RecordNotFound() { return map[string]interface{}{"message": "User not found"} }
Verify password
When we have a user, we should verify if the password that we sent is the correct one.
Inside the same function named “Login”, we need to create simple password verification by using a method “bcrypt.CompareHashAndPassword”.
Next, we need to create if statement, where we will check if our password is not mismatched and if there is no error.
If yes, we should return the message about “Wrong password”.
passErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass)) if passErr == bcrypt.ErrMismatchedHashAndPassword && passErr != nil { return map[string]interface{}{"message": "Wrong password"} }
Find accounts for the user
Now we have user, password, and all is validated, but our user is not complete yet.
We need a bank account for the user object.
To get that, we should define var accounts with a slice of type ResponseAccount.
We will assign data from the database inside this array.
Next, we need to ask the database to return fields “id”, “name”, and “balance” for the all records from the table “accounts” where “user_id” is equal to our user’s id.
accounts := []interfaces.ResponseAccount{} db.Table("accounts").Select("id, name, balance").Where("user_id = ? ", user.ID).Scan(&accounts)
Setup responseUser
In the next step, we should set up the struct of the responseUser, and assign values to the keys.
After responseUser struct we can close DB connection.
responseUser := &interfaces.ResponseUser{ ID: user.ID, Username: user.Username, Email: user.Email, Accounts: accounts, } defer db.Close()
Sign token
One of the last and the most critical parts of the “Login” function is JWT token that we should set up, and sign.
We will use the JWT package for that.
Take a look at how it should be done in the example below.
tokenContent := jwt.MapClaims{ "user_id": user.ID, "expiry": time.Now().Add(time.Minute * 60).Unix(), } jwtToken := jwt.NewWithClaims(jwt.GetSigningMethod("HS256"), tokenContent) token, err := jwtToken.SignedString([]byte("TokenPassword")) helpers.HandleErr(err)
Prepare response
Finally! It’s the last part of the “Login” function is a response that we will return into the API.
We should set up a message as “all is fine”, our token pass to the “jwt” key, and responseUser into the “data” param.
var response = map[string]interface{}{"message": "all is fine"} response["jwt"] = token response["data"] = responseUser return response
Create an API package
Now, we can go into the API.
The first step that we should complete is a new directory named “api”, next create a file with the same name, and “go” extension.
Inside the file, we should declare a package named “api”, and import a few dependencies.
package api import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "duomly.com/go-bank-backend/helpers" "duomly.com/go-bank-backend/users" "github.com/gorilla/mux" )
Create structs in API
The second step is the struct declaration.
We should create two structs, the first one named “Login” with “Username” as a string, and “Password” as a string as well.
The last struct should be declared as “ErrResponse” with “Message” as a string.
type Login struct { Username string Password string } type ErrResponse struct { Message string }
Create a login function in API
In this step, we will create the first function for our API.
It should be named “login”, and take “w http.ResponseWriter”, and “r *http.Request” as params.
func login(w http.ResponseWriter, r *http.Request) { }
Read the body of the API call
The first logic inside the “login” function is a code that will read the body of our API request.
We should specify “body,” and “err” variables and assign “ioutil.ReadAll” with request’s body inside.
Next, we need to verify if all is fine by using “HandleErr” from the “helpers” package and passing “err” inside.
body, err := ioutil.ReadAll(r.Body) helpers.HandleErr(err)
Handle login
The most important part of this function is the proper login.
We need to define a variable with a type “Login”, and assign there Unmarshaled Request’s body.
Next, we should pass the formatted username and formatted password into the “users.Login”.
We should assign that logic to the variable named “login”.
var formattedBody Login err = json.Unmarshal(body, &formattedBody) helpers.HandleErr(err) login := users.Login(formattedBody.Username, formattedBody.Password)
Prepare a response for the API call
After calling the login, we should check if the login message is equal to the “all is fine”.
If yes, we should declare a variable named “resp”, and assign the login into it.
Next, we should use “json.NewEncoder” and encode our resp.
if login["message"] == "all is fine" { resp := login json.NewEncoder(w).Encode(resp) } else { }
Handle error in else
Now, when we handled the “if” statement, we should handle “error” as well.
To do that, we should create an “else” statement, and format a response inside it.
We need to return a “Message” with string “Wrong username or password”, as a value.
This response should be encoded as same as the positive case.
if login["message"] == "all is fine" { resp := login json.NewEncoder(w).Encode(resp) } else { resp := ErrResponse{Message: "Wrong username or password"} json.NewEncoder(w).Encode(resp) }
Create startApi function
In the last step in the API logic, we should create a proper router, and handle our API endpoints.
We will use gorilla mux to create a router, and as a first one, we will define “/login” endpoints that will accept the “POST” method.
Next, we should set up an HTTP listener on the 8888 port.
func StartApi() { router := mux.NewRouter() router.HandleFunc("/login", login).Methods("POST") fmt.Println("App is working on port :8888") log.Fatal(http.ListenAndServe(":8888", router)) }
Implement API in the main function
API is ready!
To make it work, we should implement API inside the “main” function in the “main.go” file.
Check the example below.
package main import "duomly.com/go-bank-backend/api" func main() { // migrations.Migrate() api.StartApi() }
Run API
Woohoo!
Now you can run your app and start testing it.
Open the terminal in the project’s directory and type:
go run main.go
Conclusion
Congratulations, your project has the login and the first rest API.
You can start connecting it with front-end from the course:
Angular Course with building a banking application with Tailwind CSS – Lesson 1: Start the project
If you would like to compare the code with what I’ve done here is the URL:
https://github.com/Duomly/go-bank-backend
The branch for this lesson is named “Golang-course-Lesson-2”.
See you in the next lesson when we will build user registration.
Golang course Lesson 3:
Golang course with building a fintech banking app – Lesson 3: User registration
Thanks for reading,
Radek from Duomly