This article was originally published at:
https://www.blog.duomly.com/golang-course-with-building-a-fintech-banking-app-lesson-3-user-registration
Intro
In the third lesson of the Golang course, I will show you how to build user registration in Golang.
In the previous lesson of the Golang course, we learned how to create REST API in Golang and how to create a user login.
URL here:
Golang course with building a fintech banking app - Lesson 2: Login and REST API
And in the Angular course, my friend Anna showed you how to build login in angular 9.
URL here:
Angular Course with building a banking application with Tailwind CSS – Lesson 2: Login form
So, in today's lesson, we can go into the next step.
That is user registration, but not only.
Our project is going bigger, so we need some refactoring as well.
And we need to make sure the data that we send via our API is correct, so we will need to create a validation feature.
Let's start!
And if you prefer video, here is the youtube version:
Refactor prepareToken
The first step that we need to do is some refactoring.
In the first move, we will cut all the logic related to token and crate the new function for that.
Take the logic related to the token from the function Login in the users/users.go file.
And put that in the new function named "prepareToken" in the same file.
func prepareToken(user *interfaces.User) string {
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)
return token
}
Refactor prepareResponse
The next step is similar to the previous one.
But in the second step, we should take all logic related to the response from the login.
For that logic, we should create a function named "prepareResponse".
Le' ts move all of that login into the "prepareResponse" function.
func prepareResponse(user *interfaces.User, accounts []interfaces.ResponseAccount) map[string]interface{} {
responseUser := &interfaces.ResponseUser{
ID: user.ID,
Username: user.Username,
Email: user.Email,
Accounts: accounts,
}
var token = prepareToken(user);
var response = map[string]interface{}{"message": "all is fine"}
response["jwt"] = token
response["data"] = responseUser
return response
}
Refactor Login
Our login is cleared now but has missing variables.
We should fix it, and assign the function that we created before to the variable named "response".
Next' we need to pass the variables "user" and "accounts" as params of that function.
Let's take a look at the example below.
func Login(username string, pass string) map[string]interface{} {
// Connect DB
db := helpers.ConnectDB()
user := &interfaces.User{}
if db.Where("username = ? ", username).First(&user).RecordNotFound() {
return map[string]interface{}{"message": "User not found"}
}
// Verify 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
accounts := []interfaces.ResponseAccount{}
db.Table("accounts").Select("id, name, balance").Where("user_id = ? ", user.ID).Scan(&accounts)
defer db.Close()
var response = prepareResponse(user, accounts);
return response
}
Create a Register function
Great, refactoring is done!
Now we can go into the Register function.
In the same file, we should create a function named "Register".
That function should take username, email, and pass as params, all with type "string".
Function "Register" should return the same type of response as the "Login" function.
func Register(username string, email string, pass string) map[string]interface{} {
}
Create Validation interface
Before we continue with logic for the Register, we should create validation.
That will ensure, all variables we send are the ones that we planned to have.
The first step of the validation is to create an interface for that.
Let's go into the interfaces/interfaces.go.
Next, let's create a struct named "Validation", with the two props.
The first one will be "Value", and the second one "Valid", both as strings.
type Validation struct {
Value string
Valid string
}
Create Validation logic
Now, we can create some logic for validation.
The first step that we should do is create two regular expressions that will validate if our variables are correct.
The first one should verify if we do not pass anything else than letters or numbers.
And the second one should verify if our variable fits the email pattern.
Next, we need to create a switch-case statement and verify if the username and email pass the regexp.
The last step is to verify if our password is at least 5-chars long.
func Validation(values []interfaces.Validation) bool{
username := regexp.MustCompile(`^([A-Za-z0-9]{5,})+$`)
email := regexp.MustCompile(`^[A-Za-z0-9]+[@]+[A-Za-z0-9]+[.]+[A-Za-z]+$`)
for i := 0; i < len(values); i++ {
switch values[i].Valid {
case "username":
if !username.MatchString(values[i].Value) {
return false
}
case "email":
if !email.MatchString(values[i].Value) {
return false
}
case "password":
if len(values[i].Value) < 5 {
return false
}
}
}
return true
}
Add Validation to Login
When we finished validation, we can add that logic to the login.
Inside the "Valid" function, we should pass all the variables that we need to check.
Don't forget about the name of the "Valid" key.
We should add an if-else statement, and put the logic of the "Login", when if-else passed.
If not, we should return a message with the status "not valid values
".
func Login(username string, pass string) map[string]interface{} {
// Add validation to login
valid := helpers.Validation(
[]interfaces.Validation{
{Value: username, Valid: "username"},
{Value: pass, Valid: "password"},
})
if valid {
// Connect DB
db := helpers.ConnectDB()
user := &interfaces.User{}
if db.Where("username = ? ", username).First(&user).RecordNotFound() {
return map[string]interface{}{"message": "User not found"}
}
// Verify 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
accounts := []interfaces.ResponseAccount{}
db.Table("accounts").Select("id, name, balance").Where("user_id = ? ", user.ID).Scan(&accounts)
defer db.Close()
var response = prepareResponse(user, accounts);
return response
} else {
return map[string]interface{}{"message": "not valid values"}
}
}
Add Validation to Register
This step is very similar to the previous one.
If all is fine, we can start Register logic. If not, return the same message as in the "Login".
One small difference is to add one more variable "email".
func Register(username string, email string, pass string) map[string]interface{} {
// Add validation to registration
valid := helpers.Validation(
[]interfaces.Validation{
{Value: username, Valid: "username"},
{Value: email, Valid: "email"},
{Value: pass, Valid: "password"},
})
if valid {
} else {
return map[string]interface{}{"message": "not valid values"}
}
}
DB connection for Register
Now we can go into the DB connection.
We already did it a few times before, so you can just copy/paste this code.
db := helpers.ConnectDB()
Create user and account
We created this one feature as well.
You can just take a look at the migrations/migrations.go into the createAccounts.
You can copy that code or write it from scratch.
If you copy the code, you will need to change some values.
Let's take a look at the example below.
generatedPassword := helpers.HashAndSalt([]byte(pass))
user := &interfaces.User{Username: username, Email: email, Password: generatedPassword}
db.Create(&user)
account := &interfaces.Account{Type: "Daily Account", Name: string(username + "'s" + " account"), Balance: 0, UserID: user.ID}
db.Create(&account)
defer db.Close()
Prepare response and token
Response and token will be very similar to the logic from the "Login".
You just need to add "prepareResponse" function to the variable named "response.
Next, "response" should be at the end of the if, in the "Register" function.
accounts := []interfaces.ResponseAccount{}
respAccount := interfaces.ResponseAccount{ID: account.ID, Name: account.Name, Balance: int(account.Balance)}
accounts = append(accounts, respAccount)
var response = prepareResponse(user, accounts)
return response
Create Register interface
Congratulations!
The logic for the "Register" function is ready. Now you can go into the logic for the API.
We need to move into the "api/api.go" file.
Next, we should create an interface named "Register", and define there "Username", "Email", and "Password".
We should define all props as "string".
type Register struct {
Username string
Email string
Password string
}
Create a register function in API
In the next step of creating API, we should create a function named "register".
That function should take the same params as function "logic".
You can copy the whole function named "logic", and just change a few places.
There will be the same reading body.
Next, the function should call the "Register" from the users.
As the last step, our "register" logic should prepare the response and handle an error.
Let's take a look at the example below.
func register(w http.ResponseWriter, r *http.Request) {
// Read body
body, err := ioutil.ReadAll(r.Body)
helpers.HandleErr(err)
// Handle registration
var formattedBody Register
err = json.Unmarshal(body, &formattedBody)
helpers.HandleErr(err)
register := users.Register(formattedBody.Username, formattedBody.Email, formattedBody.Password)
// Prepare response
if register["message"] == "all is fine" {
resp := register
json.NewEncoder(w).Encode(resp)
// Handle error in else
} else {
resp := ErrResponse{Message: "Wrong username or password"}
json.NewEncoder(w).Encode(resp)
}
}
Create API endpoint
Great!
Now is the last step.
Now we just need to create one line of code to handle the "/register" endpoint.
Add the line with "HandleFunc" for that route in the function named "StartApi".
You can take a look at how we made it with the "/login".
func StartApi() {
router := mux.NewRouter()
router.HandleFunc("/login", login).Methods("POST")
router.HandleFunc("/register", register).Methods("POST")
fmt.Println("App is working on port :8888")
log.Fatal(http.ListenAndServe(":8888", router))
}
Conclusion
Congratulations, your project has user registration and validation now!
You can start connecting it with front-end from the course:
Learn Angular 9 with Tailwind CSS by building fintech banking app
If you would like to compare the code with what I've done here is the URL:
https://github.com/Duomly/go-bank-backend/tree/Golang-course-Lesson-3
The branch for this lesson is named "Golang-course-Lesson-3".
See you in the next lesson when we will focus on the user profile and start building money transfers.
Thanks for reading,
Radek from Duomly
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.