Webservices with Go — ReST server with Json/HTTP
By: Prateek Gupta
Tags: Database, Json, Web Services, Golang, Http Server

I have started working on building webservices in go. This has been a great learning experience and will be sharing my learnings in this series which I am calling Webservices with Go. This will mostly be a 4 post series with 2 posts covering setting up webservice in Go and creating ReST APIs with JSON-over-HTTP. The latter 2 posts covering RPC based server using protobuf and grpc and how to support both RPC and ReST using a gateway.

Why webservice? Because APIs are one of the best method to access resources of a system. Any device can interact with another device using simple APIs.

This post assumes that you already have knowledge about ReST API development over HTTP protocol with JSON as message exchange format. Though the post uses the stated technologies, you can understand the principles and tweak with your own choice of technology.

We will be using the gin-gonic library in Go for adding ReST support. Lets start…

Setting up the base repo

Let the projecy directory be example.com. Inside example we create application hello. So we have base directory structure as example.com/hello. Inside this, I have chosen the package structure as

config --

config.go

constants --

constants.go

controller --

router.go

health_controller.go

dao --

db.go

datastore.go

server --

primary_server.go

main.go

Go Modules

Firstly we need to have a dependency management system. Here we use Go modules.A module is a collection of Go packages stored in a file tree with a go.mod file at its root.

go mod init example.com/hello

This will setup the go modules for dependency management and create a go.mod file. For understand the magic of modules, whenever you want to use any go library, all you need to run is the command

go get <dependency> go get github.com/go-sql-driver/mysql

Tada….And your application has added go library for go’s sql driver for mysql db.

From here, this post will assume that whenever you need a new library you are able to google and go get the dependency in your project. I will try to highlight though.

Setting up application configs

Lets start with setting up bare minimum configs for our db which in my case is a mysql, environment variables, server configs.

constants.go

package constants const (

ServerPort = "SERVER_PORT" DbUser = "DB_USER"

DbPassword = "DB_PASSWORD"

DbHost = "DB_HOST"

DbPort = "DB_PORT"

DbDatabase = "test"

) config.go

package config import (

"hello/constants"

"log"

"os"

) var DefaultServerPort = getEnv(constants.ServerPort, "8080") var DefaultDbUser = getEnv(constants.DbUser, "root")

var DefaultDbPassword = getEnv(constants.DbPassword, "realpassword")

var DefaultDbHost = getEnv(constants.DbHost, "localhost")

var DefaultDbPort = getEnv(constants.DbPort, "3306")

var DefaultDbDatabase = getEnv(constants.DbDatabase, "test") func getEnv(key string, fallback string) string {

if envString := os.Getenv(key); envString != "" {

log.Print(key, envString)

return envString

}

log.Print(key, fallback)

return fallback

} func GetEnvString(key string, fallback string) string {

return getEnv(key, fallback)

} db.go

package db import (

"database/sql"

_ "github.com/go-sql-driver/mysql"

"log"

"time"

) type DatabaseConfig struct {

DriverName string

Username string

Password string

Host string

Port string

Database string

MaxIdleConnections int

MaxOpenConnections int

MaxConnLifetime time.Duration

} type Connector interface {

setConnection(conn *sql.DB)

} type DBStruct struct {

Conn *sql.DB

Config DatabaseConfig

} func (d *DBStruct) setConnection(conn *sql.DB) {

d.Conn = conn

} func getDbConf(conf RdsConfig) string {

return conf.Username + ":" + conf.Password + "@tcp(" + conf.Host + ":" + conf.Port + ")/" + conf.Database

} func (d *DBStruct) connectDB() {

log.Print("Connecting to DB : ", d.Config.Host)

db, err := sql.Open(d.Config.DriverName, getDbConf(d.Config))

if err != nil {

log.Print("DB NOT CONNECTED")

log.Println(err)

return

}

db.SetMaxOpenConns(50)

db.SetConnMaxLifetime(time.Minute)

db.SetMaxIdleConns(25)

d.setConnection(db)

}

Lets understand what did we do with these 3 files :

In constants.go, we define the centralised constants that can be used across the application. Herein, we have define the constants that map to the environment variables that will be used to supply configs to our application. We have defined the server port and database properties. In config.go, we define the getEnv() function which try to get the environment variable properties using the os package of Go. In cases where the values are not provided separately, we have defined a default value for the same. In db.go, I have defined the based structures which are used for establishing connection to the database via DatabaseConfig struct I have defined the DBStruct struct which holds a pointer to the sql.DB and a reference to DatabaseConfig struct. The sql.DB comes from the database/sql package of Go. Also observe the github.com/go-sql-driver/mysql imported because that is internally required by database/sql package.

These are all the basic configurations that the application needs. The next job is to move to initialise our connect to our database, server and main.

Connection to DB

datastore.go

package db import (

"context"

"hello/config"

"hello/constants"

) type Datastore struct {

Data *DBStruct

} func InitialiseDatastore(ds *Datastore) {

dbConfig := DatabaseConfig{

DriverName: "mysql",

Username: config.GetEnvString(constants.DelDbMasterUser, config.DefaultDelDbMasterUser),

Password: config.GetEnvString(constants.DelDbMasterPassword, config.DefaultDelDbMasterPassword),

Host: config.GetEnvString(constants.DelDbMasterHost, config.DefaultDelDbMasterHost),

Port: config.GetEnvString(constants.DelDbMasterPort, config.DefaultDelDbMasterPort),

Database: config.GetEnvString(constants.DelDbMasterDatabase, config.DefaultDelDbMasterDatabase),

}

ds.Data = db.DBStruct{

Config: dbConfig,

}

ds.Data.connectDB()

}

Setting up base server

primary_server.go

package servers import (

"hello/config"

"hello/constants"

"hello/db"

"github.com/gin-gonic/gin"

"log"

"net/http"

"os"

"os/signal"

"time"

) type Server struct {

Name string

Dao *db.Datastore

} func Start(app *Server, router *gin.Engine) {

app.Dao = new (db.Datastore)

initDatastore(app.Dao)

engageGinServer(router)

} func initDatastore(dao *db.Datastore) {

ds.InitialiseDatastore(dao)

} func engageGinServer(router *gin.Engine) {

log.Print("Starting Server...")

srv := &http.Server{

Addr: ":" + config.GetEnvString(constants.ServerPort, config.DefaultServerPort),

Handler: router,

} go func() {

if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {

log.Fatalf("listen: %s

", err)

}

}() quit := make(chan os.Signal)

signal.Notify(quit, os.Interrupt)

<-quit

log.Println("Shutdown Server ...")

ShutDown(srv)

}

Setting up main to start server

main.go

package main import (

"hello/controllers"

"hello/server"

"github.com/gin-gonic/gin"

) func main() {

engage(&server.Server{Name: "Basic"},

gin.Default())

} func engage(app *server.Server, router *gin.Engine) {

//controllers.Routes(router)

server.Start(app, router)

}

You can now try to build and run your server. I would suggest to maintain a makefile in the root folder and create a runnable task there.

makefile

app:

go build hello

./hello

Just run make app command in your terminal and the server should be setup. If the server is up and running then CONGRATS!!!

NOTE : Observe that controller.Routes is commented. That is because we have not added controller/routes in the project yet.

Though it is not of much use as of now because we have not added any APIs yet. In my next post we will learn to create some APIs and then our server becomes useful.

I will try to finish webservice in Go in the next post mostly by learning to add APIs using controller, routes and handlers. Till then keep learning.