Echo Framework

Fast and unfancy HTTP server framework for Go (Golang). Up to 10x faster than the rest.

-echo

This code isn’t a best practice how to code in Golang, but i just want to apply what i learned to remind me.

At first, we need to think about the folders and file structure for our project. i want to follow the MVC architecture. Base on MVC architecture, we should have model, views and controllers. for the API, i think we might not need views.

Let’s start by catching a glimpse of the File Structure of the project :

app-name/
|- controller/ - Contains main API logic files
|- LoginController.go - Defines handler methods of endpoints
|- model
|- model.go - User models
|- config - Contains all configuration files
|- config.json
|- db
|- db.go - Contains database configuration
|- helper
|- util.go - Contains database configuration
|- server.go - Entry point of the API

create /model/models.go

package modelstype Users struct {
Username string `json:"username"`
Firstname string `json:"firstname,omitempty"`
Lastname string `json:"lastname,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
DateCreated string `json:"date_created,omitempty"`
}
type Error struct {
ResponseCode int `json:"rc"`
Message string `json:"message"`
Detail string `json:"detail"`
ExternalReference string `json:"ext_ref"`
}

for registrasi user we need to define user model. and it is defined inside model/model.go. Along with user model we will define Error model in which will store the error result of few response object.

now we need to create database connection. create dbConnection.go inside folder db

package dbtype DBConfig {
Host string `json: "host"`
Port string `json: "port"`
User string `json: "user"`
Password string `json: "password"`
Dbname string `json: "dbname"`
}
func DB() *sql.DB { var configuration *DBConfig
plan, err := ioutil.ReadFile("config/config.json")
if err != nil {
log.Error(err)
}
json.Unmarshal(plan, &configuration)
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", configuration.Host, configuration.Port, configuration.User, configuration.Password, configuration.Dbname)

result, err := sql.Open("postgres", psqlInfo)
if err != nil {
log.Fatalf("Tidak dapat konek ke database : %s", err)
}
return result
}

after define model and db, we can use those inside handler function. the functions are defined inside controller/LoginController.go

for handle registration :

func Registration(c echo.Context) error {var user models.Users
body, _ := ioutil.ReadAll(c.Request().Body)
err := json.Unmarshal(body, &user)
if err != nil {
return err
}
DB := db.DB()
var count int
sqlStatement := QUERY_COUNT_REGISTRATION
err = DB.QueryRow(sqlStatement, user.Username).Scan(&count)
if err != nil {
return err
}
if count > 0 {
log.Error("username already used")
resp := c.JSON(http.StatusConflict, helper.ErrorLog(http.StatusConflict, "username already used", "EXT_REF"))
return resp
}
//hashing password
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
if err != nil {
resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, " Error While Hashing Password", "EXT_REF"))
return resp
}
user.Password = string(hash)
user.DateCreated = helper.DateTime()
user.Token = helper.JwtGenerator(user.Username, user.Firstname, user.Lastname, "secretkey")
stmt, err := DB.Prepare(QUERY_INSERT_REGISTRATION)
if err != nil {
resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Error when prepare statement : "+err.Error(), "EXT_REF"))
return resp
}
_, err = stmt.Exec(user.Username, user.Firstname, user.Lastname, user.Password, user.Token, user.DateCreated)if err != nil {log.Error(err)resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Error when execute statement : "+err.Error(), "EXT_REF"))return resp}resp := c.JSON(http.StatusOK, user)
log.Info()
return resp
}

for handle login :

func LoginController(c echo.Context) error {
var user models.Users
payload, _ := ioutil.ReadAll(c.Request().Body)
err := json.Unmarshal(payload, &user)

if err != nil {
log.Error(err)
return err
}
var result models.Users
DB := db.DB()
sqlStatement := QUERY_SELECT_LOGIN

err = DB.QueryRow(sqlStatement, user.Username).Scan(&result.Username, &result.Firstname, &result.Lastname, &result.Password, &result.Token, &result.DateCreated)
if err != nil {
log.Error(err)
resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Invalid Username", "EXT_REF"))
return resp
}

err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(user.Password))
if err != nil {
log.Error("Invalid Password :", err)
resp := c.JSON(http.StatusInternalServerError, helper.ErrorLog(http.StatusInternalServerError, "Invalid Password", "EXT_REF"))
return resp
}

result.Token = helper.JwtGenerator(result.Username, result.Firstname, result.Lastname, "secret")

//resp
resp := c.JSON(http.StatusOK, result)
log.Info()
return resp
}

now created util.go inside folder helper

package helper

import (
"bookingapp/models"
"time"

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

func DateTime() string {
currentTime := time.Now()
result := currentTime.Format("2006-01-02 15:04:05") //yyyy-mm-dd HH:mm:ss
return result
}

func JwtGenerator(username, firstname, lastname, key string) string {
//Generate Token JWT for auth
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": username,
"firstname": firstname,
"lastname": lastname,
})

tokenString, err := token.SignedString([]byte(key))
if err != nil {
return err.Error()
}
return tokenString
}

func ErrorLog(rc int, detail, ext_ref string) models.Error {
var error models.Error
error.ResponseCode = rc
error.Message = "Failed"
error.Detail = detail
error.ExternalReference = ext_ref

return error
}

define config.json for databases config :

{
"host": "127.0.0.1",
"port": "5432","user": "postgres","password": "password","dbname": "godatabase"
}

after everything is done let’s define route inside server.go

package main

import (
"bookingapp/controllers"
"net/http"
"time"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

f
unc main() {
e := echo.New()

// e.Use(middleware.Logger())
e.Use(middleware.Recover())

e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.POST},
}))

e.POST("/login", controllers.LoginController)
e.POST("/registration", controllers.Registration)
e.GET("/profil", controllers.Profil)

s := &http.Server{
Addr: ":1323",
ReadTimeout: 20 * time.Minute,
WriteTimeout: 20 * time.Minute,
}
e.Logger.Fatal(e.StartServer(s))
}

Now we can run our program to see our api in action. To run the appliction , type following command in terminal

$ go run server.go

let’s we test the api with cURL

curl --location --request POST 'localhost:1323/registration' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"martin1234",
"firstname":"martin",
"lastname":"yonatan",
"password":"password"
}

2. Login

curl --location --request POST 'localhost:1323/login' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "martin1234",
"password": "password"
}'

Thanks.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store