Loading...
Blog Home

Blog /  Tech / Easily Build a Scalable Web Application in Go

Easily Build a Scalable Web Application in Go

Easily Build a Scalable Web Application in Go

November 21, 2019 11:01 am | by Jay | Posted in Tech

I’m Jay RealBuckSavage, a lead software engineer at Sarvika Technologies, and I’m gonna show y’all how you can easily build scalable web applications in Go.

Why I LOVE Go

I’ve been in software development for a long time. I started out as a hobbyist, writing mods for GoldSrc and Source game engines by Valve. Later I turned towards writing desktop and network based programs, and now I’m writing web applications for businesses having massive scale at Sarvika.

During this time, I’ve had so many brutal death battles with a lot of server-side languages like Java, Scala, Kotlin, Ruby, and Python. I started working with Go a few months ago and boy I’m sold. Here’s why I love Go.

  • Easy everything: Go applications are easy to understand and write, and I just can’t stop loving the dependency management system. Proofreaders will miss this text
  • Fast: Go code compiles fast, and it runs fast. Let’s admit that we don’t like to wait for a build process to finish!
  • Efficient: Since Go code is easy to understand, it’s fairly easy to write our own. Go allows you to be productive instead of putting time into things like deciding what package your class should go in.
  • Gophers!: At least it’s not an ugly mug of coffee…

Getting our hands dirty

We’ve had our share of chitchat, now to write some code. I’m assuming that you already have Go installed on your machine and know about Go‘s directory structure. Also, I’m using Archlinux (btw) to write all code for this blog post, if that matters.

This project is available on my GitHub (since I’m not going to put out each line of code in this post).

Libraries & Tools I’ll be using

  • Gin: Rest API Backend
  • Gorm: ORM of choice
  • Docker & Compose: Packaging/Deployment

Setting up the project

$ GH_USER=realbucksavage
$ mkdir -p $(go env GOPATH)/github.com/${GH_USER}/todos
$ cd $(go env GOPATH)/github.com/${GH_USER}/todos
$ go get github.com/gin-gonic/gin
$ go get github.com/jinzhu/gorm
Be sure to replace GH_USER with your GitHub username. I’m using realbucksavage since that’s what I have.

Getting started

A very basic Gin app looks like this:

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()

	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})

	if err := r.Run(":8080"); err != nil {
		panic(err)
	}
}

Gin provides us with an easy to understand routing mechanism. This snippet is giving an insight of what we’re dealing with. You can go ahead and run this program and do a quick curl --silent localhost:8080/ping | jq . to look at our pretty JSON response.

But this is not enough for what we need. It’s a working program but we need something more, so we’ll refactor. This is how the project ended up looking like by the time I completed it.

.
├── api
│   ├── api.go
│   └── todos
│       ├── todos.ctrl.go
│       └── todos.go
├── database
│   ├── database.go
│   ├── inject.go
│   └── models
│       ├── migrate.go
│       └── todo.go
├── docker-compose.yml
├── Dockerfile
├── Gopkg.lock
├── Gopkg.toml
├── lib
│   ├── common
│   │   └── json.go
│   └── utils.go
├── main.go
└── README.md

Setting up routes

I like to divide my implementations in their own packages and let these packages register their own routes. So I’m going to create a package api and let it register all routes to /api. Also, there’s going to be a subpackage todos to handle all routes to /api/todos.

Inspired by this GCUK talk.
// From api/api.go
func ApplyRoutes(r *gin.Engine) {
	todosRoute := r.Group("/api")
	todos.ApplyRoutes(todosRoute)
}

// From api/todos/todos.go
func ApplyRoutes(r *gin.RouterGroup) {
	api := r.Group("/todos")
	{
		api.POST("/", create)

		api.GET("/", list)
		api.GET("/:id", get)

		api.DELETE("/:id", remove)

		api.PATCH("/:id", update)
	}
}

Nice and easy. create, list, get, remove, and update functions are declared in api/todos/todos.ctrl.go. I like to call them controller functions, and that’s where this naming convention is coming from. Can’t blame me, I’m still a Java boi at core ?

I’m not putting these functions in this post for obvious reasons, but you can check the Github Repo out.

Setting up the database layer

So we have our app’s skeleton ready. I like to introduce a DB layer at this time.

// From database/database.go
func InitDb() *gorm.DB {
	dbConfig := "sslmode=disable host=db port=5432 dbname=todos user=tduser password=tdpass"
	db, err := gorm.Open("postgres", dbConfig)
	if err != nil {
		panic(err)
	}

	db.LogMode(true)
	models.RunMigrations(db)

	return db
}

Easy as that, but we still need to pass this DB reference to our controller functions. We’ll use a simple middleware for that. And now that everything else is in place, we will modify main.go as well.

// From database/inject.go
func Inject(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("db", db)
		c.Next()
	}
}

// main.go
func main() {
	db, _ := database.InitDb()

	r := gin.Default()
    // Apply the middleware that injects DB reference.
	r.Use(database.Inject(db))

	api.ApplyRoutes(r)

	if err := r.Run(":8080"); err != nil {
		fmt.Printf("Cannot start server: %v\n", err)
	}
}

And now, the controller functions can use the database connection like this:

func list(c *gin.Context) {
	db := c.MustGet("db").(*gorm.DB)
	// ...
}

Build and run

This application is now ready to be built and run. I’ll use docker and docker-compose to do this job since we have a dependency on a postgres database server. Also, we will need to vendor this app before we containerize it. For this, I’ll use godep.

$ dep init
$ dep ensure

Now, to containerize, I’ll lay down my Dockerfile like this:

FROM golang:1.13-alpine
EXPOSE 8080

ENV GOPATH=/go
RUN mkdir -p $GOPATH/src/github.com/realbucksavage/todos
COPY . $GOPATH/src/github.com/realbucksavage/todos

WORKDIR $GOPATH/src/github.com/realbucksavage/todos
RUN go build -o todos .

CMD ["/go/src/github.com/realbucksavage/todos/todos"]

And docker-compse.yml like this:

version: '3'

services:
  db:
    container_name: todosdb
    hostname: db
    image: postgres
    environment:
      POSTGRES_USER: tduser
      POSTGRES_PASSWORD: tdpass
      POSTGRES_DB: todos

  app:
    image: todosapi
    container_name: todosapi
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    depends_on:
      - db

Great! Now let’s do a quick docker-compose up to see what happens.

Container Crash

The application’s container crashed because it attempted to connect to postgres while it was still starting up. This means that we’ll have to implement a mechanism to attempt a connection only when our database comes up as healthy. In the earlier versions of compose, the depends_on parameter could be configured to check a container’s health status, but it has been removed in version 3. Great, now we’ll have to improvise.

To cope up with this, I implemented a lazy mechanism in database.go that keeps on trying to make a connection to the database for a limited number of times before letting the app to crash.

func InitDb() *gorm.DB {
	db := openConnection()

	db.LogMode(true)

	models.RunMigrations(db)
	return db
}

func openConnection() *gorm.DB {
	maxRetries := 3
	waitTime := 5 // In seconds

	for i := 1; i <= maxRetries; i++ {
		fmt.Printf("Opening Connection; Attempt %d of %d...\n", i, maxRetries)
		dbConfig := "sslmode=disable host=db port=5432 dbname=todos user=tduser password=tdpass"

		db, err := gorm.Open("postgres", dbConfig)
		if err != nil {
			fmt.Printf("Cannot open connection (retrying in %ds): %v\n", waitTime, err)
			time.Sleep(time.Duration(waitTime) * time.Second)
			continue
		}

		return db
	}

	panic(fmt.Errorf("Cannot open database connection after %d retries!\n", maxRetries))
}

Let’s try again.

$ docker-compose build && docker-compose up

worked

Aaaaaaaaaaand it worked.


So we have a simple REST API server implemented in Go, but there’s still a lot more to do before we can spin this up in production. Also, there will be more parts coming up soon. Subscribe to Sarvika’s newsletter to stay tuned and see more content from us 🙂


The source code for this project is available on GitHub. If you see anything out of place or wish to improve something, please submit a PR or use the issues board to contact me.


Thanks for reading, here’s a gopher eating sweet potato.

GoPotato

Written by Jay

Software Architect

Jay is a SoftwareArchitect at Sarvika Technologies, who fell in love with coding while developing mods for online games. He believes that an individual is defined by mindset and not by degrees. The software quality is of prime importance to Jay, an approach that helps him look at the bigger picture and build sustainable & sophisticated software like CLOWRE.

Related Post