Writing and running Go api's in Docker



In this post we'll be looking at creating a really basic API in Go, and running it within Docker.

Folder structure:

api/Dockerfile  
api/server.go  
api/controllers/product_controller.go  
api/models/product.go  
api/models/database.go  
docker-compose.yml  

Let's start with our docker-compose.yml

api:  
  build: ./api
  ports: 
    - 3000:3000
  volumes:
    - ./api:/go/src/github.com/EwanValentine/project/api
  links:
    - database
  environment:
    - DB_NAME=project

database:  
  image: mongo:3.0
  command: mongod --smallfiles --quiet --logpath=/dev/null

So here, we tell our Docker instance to find the Dockerfile in ./api, build it and configure some of our runtime settings. Such as mounting our project as a volume, so the container has access to the files. Exposing the port that our app's running on and linking our database container to our application container.

We're also calling in the default MongoDB container.

Now let's take a look at our API Dockerfile:

FROM golang:latest

# Copy the local package files to the container’s workspace.
ADD . /go/src/github.com/EwanValentine/project/api

# Install our dependencies
RUN go get github.com/go-martini/martini  
RUN go get github.com/martini-contrib/binding  
RUN go get github.com/martini-contrib/render  
RUN go get labix.org/v2/mgo  
RUN go get labix.org/v2/mgo/bson

# Install api binary globally within container 
RUN go install github.com/EwanValentine/project/api

# Set binary as entrypoint
ENTRYPOINT /go/bin/api

# Expose default port (3000)
EXPOSE 3000 

Now let's dive into the application code.

First up we have our server.go file. This will start the http server, configure our routes and assign the correct controller etc to each route.

package main

import (  
    "github.com/EwanValentine/project/api/controllers"
    "github.com/EwanValentine/project/api/models"
    "github.com/go-martini/martini"
    "github.com/martini-contrib/binding"
    "github.com/martini-contrib/render"
)

func main() {

    m := martini.Classic()
    m.Map(models.Database())
    m.Use(render.Renderer())

    pc := controllers.NewProductController(models.Database())

    m.Get("/products", binding.Bind(models.Product{}), pc.GetAllProducts)
    m.Post("/products", binding.Bind(models.Product{}), pc.PostProduct)
    m.Run()
}

What we're doing here is, calling the main function, which acts as an entrypoint to your application. Within that function, we're calling martini.Classic() which is our base web framework library.

Martini gives us some useful stuff, such as middlewares and routing etc.

We then set an instance of a controller, and pass its methods to two routes. Finally, we run m.Run() which starts our server.

Let's build out a model. But first, we need to create an instance of our Mongo database:

package models

import (  
    "labix.org/v2/mgo"
    "os"
)

func Database() *mgo.Session {  
    session, err := mgo.Dial("project_database_1")

    if err != nil {
        panic(err)
    }

    session.SetMode(mgo.Monotonic, true)

    session.DB(os.Getenv("DB_NAME"))

    return session
}

Here we call our MongoDB connection string (project_database_1 is an alias of our database Docker container). Then we call our database name from the environment variable we have set within our docker-compose.yml. Finally, we return an instance of the MongoDB connection.

Now our model...

package models

import (  
    "labix.org/v2/mgo/bson"
)

type Product struct {  
    Id          bson.ObjectId `json:"id" bson:"_id"`
    Title       string        `form:"title" json:"title"`
    Description string        `form:"description" json:"description"`
    Price       float64       `form:"price" json:"price"`
}

This is pretty straight forward. Using structs in Go, allows us to define strict data structure, this is especially handy when defining database structures.

Now to tie all of that together, let's add a controller:

package controllers

import (  
    "github.com/EwanValentine/project/api/models"
    "github.com/martini-contrib/render"
    "labix.org/v2/mgo"
    "labix.org/v2/mgo/bson"
    "os"
)

type (  
    ProductController struct {
        session *mgo.Session
    }
)

func NewProductController(s *mgo.Session) *ProductController {  
    return &ProductController{s}
}

func (pc *ProductController) GetAllProducts(r render.Render) {  
    products := []models.Product{}
    session := pc.session.DB(os.Getenv("DB_NAME")).C("products")
    err := session.Find(nil).Limit(100).All(&products)

    if err != nil {
        panic(err)
    }

    r.JSON(200, products)
}

func (pc *ProductController) PostProduct(product models.Product, r render.Render) {  
    session := pc.session.DB(os.Getenv("DB_NAME")).C("products")

    product.Id = bson.NewObjectId()
    product.Title = product.Title
    product.Description = product.Description
    product.Price = product.Price
    session.Insert(product)

    r.JSON(201, product)
}

This is reasonably straight forward. We're defining a struct, injecting our database, creating a callable instance and injecting it into our controller's functions.

If you run $ docker-compose build && docker-compose up in your root directory, then head to localhost:3000/products (or wherever you have your local Docker pointing to). You should be able to use a tool such as Postman to add to, and retrieve a list of products.