GO Backend Service Skeleton w/Gin
Tags: go, beginners, webdev
Published: 2021-01-09 12:36:27

## Intro

Starting this post I assume that you have basic installation of GoLang on your local environment. If not you can follow steps from; [Download and Install - The Go Programming Language](https://golang.org/doc/install)

## Go Directory

After download and install steps, your go(path) directory should be like below;

![Go Path](https://dev-to-uploads.s3.amazonaws.com/i/swh90814pze8sxtqekw8.png)

You need to create your awesome project's folder under `src/` directory. If you are using GitHub to host your codes, it would be better to start project under path like;
`~/go/src/github.com/{github-username}/{project-name}`. That makes easier using go commands such as `go get`, `go install` etc.

### Go Project Directory View

Now We are ready to open our preferred Go IDE to form project directory layout and code. Here you are my layout;

![Layout](https://dev-to-uploads.s3.amazonaws.com/i/u2dics24t6xysbzjwnwt.png)

I will try to explain every file in this directory layout from top to bottom and you can start your backend project by making changes specific to you.

#### Cmd -/api

Under `cmd` we can keep starting the system scripts. In this example you can see `api/main.go` to start our local backend service. Also, you can store fetchers, data importers or fixing scripts under this directory. To run;
`go run cmd/api/main.go`, specifically for this sample.

Let's see main.go;
```
package main

import (
	"flag"

	"github.com/{github-username}/{project}/internal/api"
	"github.com/{github-username}/{project}/internal/pkg/conf"
	log "github.com/sirupsen/logrus"
)

func main() {
	env := flag.String("env", "local", "environment")
	flag.Parse()

	config, err := conf.NewConfig("config/default.yml", *env)
	if err != nil {
		log.Fatalf("Can not reead config: %v", err)
	}

	config.Env = *env
	config.DBCred = config.Mysql.Local
	config.Secret = config.JWT.Local.Secret
	if config.Env == "prod" {
		config.DBCred = config.Mysql.Prod
		config.Secret = config.JWT.Prod.Secret
	}

	log.Info("SDK API started w/Env: " + *env)
	api.Listen(config)
}
```
As you can see basically we are reading service's configuration and starting the API by using `Listen` function under api package.

#### Config -/default.yml

As you can guess, under this directory we are storing our configurations and credentials. For secrets we can think another location but basically it is OK to keep them here.

Also yml file is a personal decision, you can select your favorite format such as json, xml, etc.

```
mysql:
  local:
    host: localhost
    port: 3306
    db: project
    user: root
    password: 12345678
  prod:
    host: prod_db
    port: 3306
    db: project
    user: user
    password: strong_pass

jwt:
  local:
    secret: "test"
  prod:
    secret: "prod"

```

#### Internal

Under internal directory we store our service's base scripts. I divided the code base mainly two parts;
1. service (api) - it could be fetcher, importer, etc.
2. pkg - structs and functions that we will use for services.

##### Api (Service)

As I mentioned main functions for an API backend service locating here. Yes, main listen function, cors header function and router for this example;

base.go: by using configuration starting gin
```
// Listen : starts api by listening to incoming requests
func Listen(c *conf.Config) {
	service.Config = c

	if service.Config.Env == "prod" {
		gin.SetMode(gin.ReleaseMode)
	}
	r := gin.New()
	r.Use(CORS())

	// router.go
	route(r)

	server := &http.Server{
		Addr:         ":9090",
		Handler:      r,
		ReadTimeout:  60 * time.Second,
		WriteTimeout: 60 * time.Second,
	}

	err := server.ListenAndServe()
	if err != nil && err != http.ErrServerClosed {
		log.Fatalf("Server can not start: %v", err)
	}
}
```

CORS function that we use to set our response headers.
You can change them specifically by looking how you request and collect responses from backend service.
```
// CORS : basic cors settings
func CORS() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(204)
			return
		}
		c.Next()
	}
}
```

router.go: Classical router.
Important parts:
* endpoint to function match
* using middleware(JWTAuth()) and grouping endpoints.
```
func route(r *gin.Engine) {
	// Base
	r.GET("/api/", service.BaseHandler)

	// Register
	r.POST("/api/user", service.Register)
	r.POST("/api/login", service.Login)

	authorized := r.Group("/api/")
	authorized.Use(service.JWTAuth())
	{
		authorized.GET("token", service.Token)
		authorized.GET("user/:uid/profile", service.Profile)
	}
}
```

##### Pkg

We store our service's deep and detailed parts in here.


###### Adapters:

In my project, I am using MySQL DB only but by looking your project's requirements you can multiple adapters like a NoSQL which needs to establish a connection.

mysql.go: 
```
func NewClient(host string, port string, dbName string, uName string, pass string) (db *sql.DB) {
	db, err := sql.Open("mysql", uName+":"+pass+"@tcp("+host+":"+port+")/"+dbName)
	if err != nil {
		panic(err.Error())
	}
	return db
}
```

###### Configuration Reader:

To read our configuration file into a struct:
conf/reader.go:

```
type Config struct {
	Env   string
	Mysql struct {
		Local struct {
			Host     string `yaml:"host"`
			Port     string `yaml:"port"`
			DB       string `yaml:"db"`
			User     string `yaml:"user"`
			Password string `yaml:"password"`
		} `yaml:"local"`
		Prod struct {
			Host     string `yaml:"host"`
			Port     string `yaml:"port"`
			DB       string `yaml:"db"`
			User     string `yaml:"user"`
			Password string `yaml:"password"`
		} `yaml:"prod"`
	} `yaml:"mysql"`
	JWT struct {
		Local struct {
			Secret     string `yaml:"secret"`
		} `yaml:"local"`
		Prod struct {
			Secret     string `yaml:"secret"`
		} `yaml:"prod"`
	} `yaml:"jwt"`

	DBCred struct {
		Host     string `yaml:"host"`
		Port     string `yaml:"port"`
		DB       string `yaml:"db"`
		User     string `yaml:"user"`
		Password string `yaml:"password"`
	}
	Secret string
}

// NewConfig returns a new decoded Config struct
func NewConfig(configPath string, env string) (*Config, error) {
	// Create config structure
	config := &Config{}

	// Open config file
	file, err := ioutil.ReadFile(configPath)
	if err != nil {
		return nil, err
	}

	// Init new YAML decode
	err = yaml.Unmarshal(file, config)
	if err != nil {
		return nil, err
	}

	return config, nil
}
```

###### Entity:

Under entity directory, first of all under `entity/main.go` I preferred to keep a DB variable for usage of all possible entities. 

```
// DB : DB for all entities
var DB *sql.DB
```

And as you can guess, in `entity/user.go` we are using this DB variable for `SELECT/INSERT/UPDATE` querying functions. Also a map struct for DB user schema to make easier to read rows.

user.go:
```
// User : user entity
type User struct {
	ID          int        `json:"id"`
	Email       string     `json:"email"`
	Password    string     `json:"password"`
	Fullname    string     `json:"fullname"`
	TSLastLogin int32      `json:"ts_last_login"`
	TSCreate    int32      `json:"ts_create"`
	TSUpdate    int32      `json:"ts_update"`
	Permission  json.RawMessage `json:"permission"`
}

// GetUserByEmail : returns single user
func GetUserByEmail(email string) *User {
	user := new(User)
	row := DB.QueryRow("SELECT * from user WHERE email=?", email)
	err := row.Scan(&user.ID, &user.Email, &user.Password, &user.Fullname, &user.TSLastLogin, &user.TSCreate, &user.TSUpdate, &user.Permission)
	if err != nil {
		log.Errorln("User SELECT by Email Err: ", err)
		return nil
	}
	return user
}
```

###### Service:

Under service directory there are handlers / middleware functions. These functions are the base point to respond requests coming from any external service.

Under `service/base.go`, we can add a baseHandler to check health of the service.

```
// BaseHandler : home - health-test
func BaseHandler(c *gin.Context) {
	log.Info("Base")
}
```

Under `service/jwt.go`, you can find the middleware function to validate and auth token, that we called from router.go as it is mentioned.

```
// ValidateToken :
func ValidateToken(encodedToken string) (*jwt.Token, error) {
	return jwt.Parse(encodedToken, func(token *jwt.Token) (interface{}, error) {
		if _, isvalid := token.Method.(*jwt.SigningMethodHMAC); !isvalid {
			return nil, errors.New("invalid token")
		}
		return []byte(Config.Secret), nil
	})
}

// JWTAuth :
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		var bearer = "Bearer"
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		tokenString := authHeader[len(bearer):]
		token, err := ValidateToken(tokenString)
		if token.Valid {
			claims := token.Claims.(jwt.MapClaims)
			log.Debug(claims)
		} else {
			log.Error(err.Error())
			c.AbortWithStatus(http.StatusUnauthorized)
		}
	}
}
```

And lastly, `service/user.go`, there are functions related to user schema. Such as login, register, profile, etc. Here you are login function that we used in router.go to respond `api/login` endpoint.

```
// Login :
func Login(c *gin.Context) {
	entity.DB = adapter.NewClient(Config.DBCred.Host, Config.DBCred.Port, Config.DBCred.DB, Config.DBCred.User, Config.DBCred.Password)
	defer entity.DB.Close()

	user := new(entity.User)
	if err := c.BindJSON(user); err != nil {
		log.Error("Binding user error occured: ", err)
		c.JSON(http.StatusBadRequest, "Binding error")
		return
	}

	checkUser := entity.GetUserByEmail(user.Email)
	if checkUser == nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": 0,
		})
		return
	}

	login := comparePasswords(checkUser.Password, []byte(user.Password))
	if login == false {
		c.JSON(http.StatusForbidden, gin.H{
			"success": 0,
		})
		return
	}

	token, err := GenerateToken(checkUser.ID)
	if err != nil {
		c.JSON(http.StatusForbidden, gin.H{
			"success": 0,
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"success": 1,
		"data":    gin.H{
			"uid": checkUser.ID,
			"uemail": checkUser.Email,
			"token": token,
		},
	})
	return
}
```

As a conclusion, I am using this layout while starting any side project's backend service. By adding new entities, service functions and endpoints, it is really sufficient to save your time that is spent in starting step.

Cheers.