fullstack web app-golang #1

f

So as I already said we’re gonna create both the frontend and backend. First we’ll consider only on the backend and then we will write the front end.

spoilers:

close your eyes! Here’s the completed project code:

frontend – https://github.com/vigneshwar221B/sekretu-frontend-react

backend- https://github.com/vigneshwar221B/sekretu-go-backend

Use this as a reference to follow along the code or write alongside this article.

lets start

The project structure we’re gonna use will be little bit different.

so to run the code use, go run app/main.go

But whatever, first let’s write a basic web server with MUX.

package main

import (
	"log"
	"net/http"
	"github.com/gorilla/mux"
	"encoding/json"
)

func main() {

	// Init router
	r := mux.NewRouter()


	// Route handles & endpoints
	r.HandleFunc("/", function(w http.ResponseWriter, r *http.Request){
        json.NewEncoder(w).Encode("it worked!")
        }).Methods("GET")
	

	//start the server
	log.Fatal(http.ListenAndServe(":8080", c.Handler(r)))

}

We already gone through this code. This runs the server on localhost port 8080.

Now what we’re gonna do is to structure our project. I already did that for you. But feel free to change it, if you want.

But here’s what I did

  • app – main.go
  • db – contains all the mongodb functions we need
  • helpers – contains the routes functions and other helping functions like jwt-checker and jwt-creator
  • model – contains the schema for our database

First lets write our model

//user.go
package model

type User struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Register struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

type Login struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

Nothing to explain here. The structs have 3 fields name, email and password. and the login struct have only email and password field.

package model

import "go.mongodb.org/mongo-driver/bson/primitive"

//Post is the structure of the Post Data
type Post struct{
	ID primitive.ObjectID `bson:"_id" json:"_id,omitempty"`
	Title string `json:"title"`
	Body string `json:"body"`
}

Again same, nothing new here. Except that ID part.

So basically whenever you create a new object, mongodb creates a new unique ID for you. Most of the times, we don’t use it like what I did with user.go.

I’ll explain later why we need it, when we started building the frontend.

Now lets write routes.js in helpers folder

package helpers

import (
	"encoding/json"
	"fmt"
	"net/http"
	"web-app/db"
	"web-app/model"

	jwt "github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
)

We need 2 package here. JWT and BCRYPT. You already know about JWT.

So whenever user types the password, we don’t wanna store their actual password. So we’ll hash the password and only store that hash. To use these hashing functions we’ll use this bcrypt library


//SaveUser is for '/register' post
func SaveUser(w http.ResponseWriter, r *http.Request) {
	//decode the body

	//check if the user already register
	
	//hash the password
	
	//save it to DB

	//generate jwt 

        //send the jwt

}

// for /login route
func FindUser(w http.ResponseWriter, r *http.Request) {
	//decode the body

	//find it in the db

	//check if the user exists
	
        //compare the hash

	//generate jwt and send it

	//send the jwt 

}

First lets decode the request.

decoder := json.NewDecoder(r.Body)

var t model.Login
err := decoder.Decode(&t)
if err != nil {
	panic(err)
}

This json.newDecoder(r.Body) will give us the decoder and we will use .Decode(t) to decode it and store it in t.

Before going further lets add this in the main.go file

r.HandleFunc("/register", helpers.SaveUser).Methods("POST")
r.HandleFunc("/login", helpers.FindUser).Methods("POST")

Lets open our postman and check we can decode it.

We should see this in the console.


//SaveUser is for '/register' post
func SaveUser(w http.ResponseWriter, r *http.Request) {
        //decode the body
        decoder := json.NewDecoder(r.Body)

	var t model.Register
	err := decoder.Decode(&t)
	if err != nil {
		panic(err)
	}

	//check if the user already register

	//hash the password
	
	//save it to DB
	
	//generate jwt and send it
	
}

We’ll do the DB part later. First lets finish the JWT part. Maybe try to do it on your own. I’ll wait.

So basically I wrote the GenerateJWT() inside helpers/jwtHelper.go

package helpers

import (
	"fmt"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
)

//just write anything you want
var mySigningKey = []byte("iamtheboneofmysword")

//GenerateJWT returns the JWT
func GenerateJWT(email string) (string, error) {
	token := jwt.New(jwt.SigningMethodHS256)

	claims := token.Claims.(jwt.MapClaims)

	claims["authorized"] = true
	claims["email"] = email
	claims["exp"] = time.Now().Add(time.Minute * 60).Unix()

	tokenString, err := token.SignedString(mySigningKey)

	if err != nil {
		fmt.Errorf("Something Went Wrong: %s", err.Error())
		return "", err
	}

	return tokenString, nil
}

Now as this out of the way. Lets also hash the password. As I said what it will allow us to do is to hash the password.

As you can see the password is hashed. So how to create a hash for a given password?

/hash the password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(t.Password), bcrypt.DefaultCost)
if err != nil {
	panic(err)
}

One thing to note here is that the bcrypt.GenerateFromPassword() takes byte array as the first argument not string. so convert your string to byte array.

And how do you check the hashed password?

err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(t.Password))

if err != nil {
	//incorrect password
	json.NewEncoder(w).Encode("incorrect password")
	return
}

So now our code should look like this:

//SaveUser is for '/register' post
func SaveUser(w http.ResponseWriter, r *http.Request) {
        //decode the request body
	decoder := json.NewDecoder(r.Body)

	var t model.Register
	err := decoder.Decode(&t)
	if err != nil {
		panic(err)
	}

	fmt.Println(t)

	//check if the user already register


	//hash the password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(t.Password), bcrypt.DefaultCost)
	if err != nil {
		panic(err)
	}
	t.Password = string(hashedPassword)

	//save it to DB

	//generate jwt and send it
	token, err := GenerateJWT(t.Email)

	if err != nil {
		fmt.Println("Failed to generate token")
	}

	json.NewEncoder(w).Encode(token)

}

func FindUser(w http.ResponseWriter, r *http.Request) {
	//decode the request body
	decoder := json.NewDecoder(r.Body)

	var t model.Login
	err := decoder.Decode(&t)
	if err != nil {
		panic(err)
	}

	fmt.Println(t)

	//find it in the db


	//check if the user exists

        //compare the hashed password
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(t.Password))

	if err != nil {
		//incorrect password
		json.NewEncoder(w).Encode("incorrect password")
		return
	}
	//generate jwt and send it
	token, err := GenerateJWT(t.Email)

	if err != nil {
		fmt.Println("Failed to generate token")
	}

	json.NewEncoder(w).Encode(token)

}

And thats it for this article. We’ll start adding mongodb to this project in the next article.

Happy coding 🙂

About the author

vigneshwar

Add comment

Leave a Reply

By vigneshwar

Most common tags

%d bloggers like this: