A Simple Go Web App on Heroku with MongoDB on MongoHQ

Go is an extremely clean and fun language to work with and has a bunch of handy, modular, well documented packages out of the box. We can easily build a small web app using pure Go for handler functions, the net/http library for routing and serving and mgo as a MongoDB driver.

The simple Go web app that we will build will use MongoHQ and Heroku’s “Platform-as-a-Service,” (PaaS). At this service level, the vendor takes care of the underlying infrastructure for you, giving you only a platform with which to (build and) host your application(s). The only thing you have to worry about is your application itself.

Let us see how.

MongoDB

MongoDB is a high-performance, open source, schema-free, document-oriented database.

  • MongoDB has the same concept of a “database” with which you are likely already familiar (or a schema for you Oracle folks). Within a MongoDB instance you can have zero or more databases, each acting as high-level containers for everything else.
  • A database can have zero or more “collections“. A collection shares enough in common with a traditional “table” that you can safely think of the two as the same thing.
  • Collections are made up of zero or more “documents“. Again, a document can safely be thought of as a “row”.
  • A document is made up of one or more “fields“, which you can probably guess are a lot like “columns”.
  • Indexes” in MongoDB function much like their RDBMS counterparts.
  • Cursors” are different than the other five concepts but they are important enough. The important thing to understand about cursors is that when you ask MongoDB for data, it returns a cursor, which we can do things to, such as counting or skipping ahead, without actually pulling down data.

MongoHQ the hosted database

MongoHQ is a platform for MongoDB hosting on the web.

Sign Up

Sign up for a free account. When you fill up the online form, remember what you enter for default username and password – we will need this information later on.

Create a database

Next, create a free database in your MongoHQ account. I have created a database named godata in my account. This database is at troup.mongohq.com and port 10080.

The Mongo URI to connect to this database is: mongodb://:@troup.mongohq.com:10080/godata.

Create a collection

For my database godata I created a collection named user.

Add documents

I then added many documents to this collection. Example of a document:

name: "Stefan Klaste",
email: "klaste@posteo.de"

Dependencies

Go

I am assuming that you have Go already installed.

Workspaces

Go code must be kept inside a workspace. A workspace is a directory hierarchy with three directories at its root:

  • src contains Go source files organized into packages (one package per directory),
  • pkg contains package objects, and
  • bin contains executable commands.

The go tool builds source packages and installs the resulting binaries to the pkg and bin directories.

The src subdirectory typically contains version control repositories (such as for Git) that track the development of one or more source packages.

The GOPATH environment variable

The GOPATH environment variable specifies the location of your workspace.

To get started, create a workspace directory and set GOPATH accordingly. Your workspace can be located wherever you like. Note that this must not be the same path as your Go installation.

On my Windows computer, I have set GOPATH=C:\go_projects\go. Next I update my system environment variable PATH to include my workspace bin subdirectory i.e. PATH=%PATH%;%GOPATH%\bin;

My workspace folder structure:

C:/go_projects
+---go
    +---bin
    +---pkg
    +---src

Package paths

The packages from the standard library are given short paths such as fmt and net/http. For your own packages, you must choose a base path that is unlikely to collide with future additions to the standard library or other external libraries. If you have a GitHub account at github.com/user, that should be your base path. We will use github.com/user as our base path. Create a directory inside your workspace in which to keep source code. I have created the folder C:\go_projects\go\src\github.com\SatishTalim.

Tip: Replace SatishTalim with your own username.

mgo – Go driver for MongoDB

Of all the Go drivers available for MongoDB, mgo is the most advanced and well-maintained.

To install mgo, make sure you have the bzr command available (bzr should be installed prior to mgo) and then run:

go get labix.org/v2/mgo

Our app gomongohq.go

This is a trivial Go web app that runs on Heroku. A user is prompted to enter a name. The web app accesses a MongoDB database on MongoHQ and returns to the user the email id of the name entered, if it exists.

List of imports

Our Go program gomongohq.go is just one single go file; here’s the list of imports:

package main

import (
        "fmt"
        "html/template"
        "labix.org/v2/mgo"
        "labix.org/v2/mgo/bson"
        "net/http"
        "os"
)

Person struct

The Person struct will hold our data fetched from the database.

type Person struct {
        Name string
        Email string
}

Main

Here’s the main function where we set up the URL routes for the application, connect to the database, and start the web server:

func main() {
        http.HandleFunc("/", root)
        http.HandleFunc("/display", display)
        fmt.Println("listening...")
        err := http.ListenAndServe(GetPort(), nil)
        if err != nil {
                panic(err)
        }
}

// Get the Port from the environment so we can run on Heroku
func GetPort() string {
        var port = os.Getenv("PORT")
        // Set a default port if there is nothing in the environment
        if port == "" {
                port = "4747"
                fmt.Println("INFO: No PORT environment variable detected, defaulting to " + port)
        }
        return ":" + port
}

http.HandleFunc(“/”, root)

When a user accesses our app, the app throws up a simple form for the user to enter a name whose email id he/she wishes to see. The function root throws a page as declared by the constant rootForm:

func root(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, rootForm)
}

const rootForm = `
  <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Your details</title>
        <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.4.2/pure-min.css">
      </head>
      <body style="margin: 20px;">
        <h2>A Fun Go App on Heroku to access MongoDB on MongoHQ</h2>
        <p>This simple app will fetch the email id of a person, if it's already there in the MongoDB database.</p>
        <p>Please enter a name (example: Stefan Klaste)</p>
        <form action="/display" method="post" accept-charset="utf-8" class="pure-form">
          <input type="text" name="name" placeholder="name" />
          <input type="submit" value=".. and query database!" class="pure-button pure-button-primary"/>
    </form>
        <div>
          <p><b>© 2014 RubyLearning. All rights reserved.</b></p>
        </div>    
      </body>
    </html>
`

The rootForm uses Pure – a set of small, responsive CSS modules that you can use in every web project.

http.HandleFunc(“/display”, display)

var displayTemplate = template.Must(template.New("display").Parse(displayTemplateHTML))

func display(w http.ResponseWriter, r *http.Request) {
        // In the open command window set the following for Heroku:
        // heroku config:set MONGOHQ_URL=
        // mongodb://IndianGuru:password@troup.mongohq.com:10080/godata
        uri := os.Getenv("MONGOHQ_URL")
        if uri == "" {
                fmt.Println("no connection string provided")
                os.Exit(1)
        }

        sess, err := mgo.Dial(uri)
        if err != nil {
                fmt.Printf("Can't connect to mongo, go error %v\n", err)
                os.Exit(1)
        }
        defer sess.Close()

        sess.SetSafe(&mgo.Safe{})

        collection := sess.DB("godata").C("user")

        result := Person{}

        collection.Find(bson.M{"name": r.FormValue("name")}).One(&result)

        if result.Email != "" {
                errn := displayTemplate.Execute(w, "The email id you wanted is: " + result.Email)
                if errn != nil {
                        http.Error(w, errn.Error(), http.StatusInternalServerError)
                } 
        } else {
                displayTemplate.Execute(w, "Sorry... The email id you wanted does not exist.")
        }
}

const displayTemplateHTML = ` 
<!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>Results</title>
      <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.4.2/pure-min.css">
    </head>
    <body>
      <h2>A Fun Go App on Heroku to access MongoDB on MongoHQ</h2>
      <p><b>{{html .}}</b></p>
      <p><a href="/">Start again!</a></p>
      <div>
        <p><b>© 2014 RubyLearning. All rights reserved.</b></p>
      </div>
    </body>
  </html>
`

The function display uses the html/template package and the mgo driver to access the database on MongoHQ. If the name is found in the database the function throws a page to the user with the email id for that name.

Usage of the mgo driver revolves around the concept of sessions. To get started, obtain a session using the Dial function:

session, err := mgo.Dial(url)

This will establish one or more connections with the cluster of servers defined by the url parameter. From then on, the cluster may be queried and documents retrieved with statements such as:

c := session.DB(database).C(collection)
err := c.Find(query).One(&result)

Once the session is not useful anymore, Close must be called to release the resources appropriately.

Close terminates the session. It’s a runtime error to use a session after it has been closed.

SetSafe changes the session safety mode. If the safe parameter is nil, the session is put in unsafe mode, and writes become fire-and-forget, without error checking. The unsafe mode is faster since operations won’t hold on waiting for a confirmation.

The following statement will make the session check for errors, without imposing further constraints:

session.SetSafe(&mgo.Safe{})

Safe returns the current safety mode for the session.

DB returns a value representing the named database (in our case godata). If name is empty, the database name provided in the dialed URL is used instead. If that is also empty, “test” is used as a fallback.

C returns a value representing the named collection (in our case user).

Insert inserts one or more documents in the respective collection.

Find prepares a query using the provided document. The document may be a map or a struct value capable of being marshalled with bson. The map may be a generic one using interface{} for its key and/or values, such as bson.M, or it may be a properly typed map. Providing nil as the document is equivalent to providing an empty document such as bson.M{}.

Further details of the query may be tweaked using the resulting Query value, and then executed to retrieve results using methods such as One, For, Iter, or Tail.

One executes the query and unmarshals the first obtained document into the result argument. The result must be a struct or map value capable of being unmarshalled into. This function blocks until either a result is available or an error happens.

Package bson is an implementation of the BSON specification for Go.

M is a convenient alias for a map[string]interface{} map, useful for dealing with BSON in a native way. For instance: bson.M{"a": 1, "b": true}

Here’s the full code of our program:

Run the program locally

Make a new folder and cd to it as follows:

$ mkdir $GOPATH/src/github.com/SatishTalim/gomongohq
$ cd $GOPATH/src/github.com/SatishTalim/gomongohq

In this folder, copy the above file gomongohq.go.

Now you can run the program with the go tool:

$ cd $GOPATH/src/github.com/SatishTalim/gomongohq
$ set MONGOHQ_URL=mongodb://IndianGuru:password@troup.mongohq.com:10080/godata
$ go run gomongohq.go
listening...
INFO: No PORT environment variable detected, defaulting to 4747

Note: Please change the username IndianGuru and password above to your own username and password. Also troup.mongohq.com:10080 may be different for you.

You can access the URL locally in your browser: http://localhost:4747

The program would present a page that asks you to enter a name. Enter a name and click on the button. The app then throws a new page with the email id for the name you entered or an error message.

Great, it worked! Let us now deploy our app to Heroku.

Deploy app to Heroku

Create an account on Heroku

Please ensure that you are connected to the internet and then create an account on Heroku (obviously do this only once). If you don’t have one, then signup. It’s free and instant. A free account can have upto 5 apps without registering your credit card.

Install the Heroku Toolbelt

The Heroku Toolbelt ensures that you have access to the Heroku command-line client, Foreman which is used to run applications locally, and the Git revision control system that is used to deploy sites to Heroku.

Once installed, you’ll have access to the heroku command from your command window. Log in using the email address and password you used when creating your Heroku account:

$ cd $GOPATH/src/github.com/SatishTalim/gomongohq
$ heroku login
Enter your Heroku credentials.
Email: satish@rubylearning.org
Password:
Could not find an existing public key.
Would you like to generate one? [Yn]
Generating new SSH public key.
Uploading ssh public key /Users/satish/.ssh/id_rsa.pub

Create a Procfile

Create a Procfile to tell Heroku what command to run for the web process in our app. The Procfile should be created in the folder $GOPATH/src/github.com/SatishTalim/gomongohq and contains:

web: gomongohq

Use Git

In order to deploy to Heroku we’ll need the app stored in Git. In the same folder i.e. $GOPATH/src/github.com/SatishTalim/gomongohq type:

$ git init
$ git add -A .
$ git commit -m "code"

Install Godep

The recommended way to manage Go package dependencies on Heroku is with Godep, which helps build applications reproducibly by fixing their dependencies.

Let us install Godep:

$ go get github.com/kr/godep

Now save your dependencies:

$ godep save

This will save a list of dependencies to the file Godeps/Godeps.json, and copy their source code into Godeps/_workspace.

Add these new files to git:

$ git add -A .
$ git commit -m "dependencies"

Now we’re ready to ship this to Heroku.

Heroku deploy

Create a new Heroku app, telling it to use the Go Heroku Buildpack to build your Go code:

$ heroku create -b https://github.com/kr/heroku-buildpack-go.git
Creating tranquil-bastion-1774... done, stack is cedar
BUILDPACK_URL=https://github.com/kr/heroku-buildpack-go.git
http://tranquil-bastion-1774.herokuapp.com/ | git@heroku.com:tranquil-bastion-1774.git
Git remote heroku added

Push the code to Heroku:

$ git push heroku master
Initializing repository, done.
Counting objects: 11, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (11/11), 1.29 KiB | 0 bytes/s, done.
Total 11 (delta 0), reused 0 (delta 0)

-----> Fetching custom git buildpack... done
-----> Go app detected
-----> Installing go1.2... done
-----> Running: godep go install -tags heroku ./...
-----> Discovering process types
       Procfile declares types -> web

-----> Compressing... done, 1.7MB
-----> Launching... done, v4
       http://tranquil-bastion-1774.herokuapp.com deployed to Heroku

To git@heroku.com:tranquil-bastion-1774.git
 * [new branch]      master -> master

Set MONGOHQ_URL just once:

$ heroku config:set MONGOHQ_URL=mongodb://IndianGuru:password@troup.mongohq.com:10080/godata

Note: Please change the username IndianGuru and password above to your own username and password. Also troup.mongohq.com:10080 may be different for you.

Your app should be up and running. Visit it with:

$ heroku open
Opening tranquil-bastion-1774... done

That’s it – you now have a running Go web app on Heroku!

You can visit this app here.

I hope this example gives a little more insight to Go newbies, at how to build real web applications in Go.

Do check out William Kennedy’s blog post “Running MongoDB Queries Concurrently With Go” where he talks about sessions.

Feel free to ask questions and give feedback in the comments section of this post. Thanks!

About indianguru

http://satishtalim.com/
This entry was posted in MongoDB, Tutorials and tagged , , , . Bookmark the permalink.

3 Responses to A Simple Go Web App on Heroku with MongoDB on MongoHQ

  1. Reblogged this on narutosanjiv and commented:
    Go sample webapp with mongodb on mongohq

  2. Pingback: [Go][Heroku] 第一個Hello world Golang Web App in Heroku | Blog E – Attitude is everything

  3. Pingback: [Golang][MongoDB] 練習一下MongoDB 與Golang 的基本連結 | Blog E – Attitude is everything

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s