DEV Community

Cover image for Learn Golang by building a fintech banking app - Lesson3: User registration
Duomly
Duomly

Posted on • Edited on • Originally published at blog.duomly.com

Learn Golang by building a fintech banking app - Lesson3: User registration

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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{} {

}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"}
    }
}
Enter fullscreen mode Exit fullscreen mode

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"}
    }

}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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))

}
Enter fullscreen mode Exit fullscreen mode

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.

Programming courses online

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.