Go and MongoDB on MongoLab and Heroku
Go and MongoDB on MongoLab and Heroku
Satish Talim
Buy on Leanpub

Preface

Who is the book for?

Are you new to Go and taking part in the Go Challenge? Some of the challenges might require you to write an app in Go that uses MongoDB on MongoLab and Heroku. Don’t know how? If yes, then this eBook is for you and will show you how it’s done.

What will you learn?

In the end, you will understand and know how to write an app in Go that uses MongoDB on MongoLab and Heroku.

Using Code Examples

All of the code in this book can be used pretty much anywhere and anyhow you please.

How to Contact Me

I can be reached via e-mail at golangchallenge@gmail.com. Please contact me if you have any questions, comments, kudos or criticism on the eBook. Constructive criticism is definitely appreciated; I want this eBook to get better through your feedback.

Thanks

Thanks for downloading and checking out this eBook. As part of the lean publishing philosophy, you’ll be able to interact with me as the eBook is completed. I’ll be able to change things, reorganize parts, and generally make a better eBook. I hope you enjoy.

Go and MongoDB on MongoLab and Heroku

What’s NoSQL?

In the words of Karl Seguin:

NoSQL is a broad term that means different things to different people. Personally, I use it very broadly to mean a system that plays a part in the storage of data. Put another way, NoSQL (again, for me), is the belief that your persistence layer isn’t necessarily the responsibility of a single system. Where relational database vendors have historically tried to position their software as a one-size-fits-all solution, NoSQL leans towards smaller units of responsibility where the best tool for a given job can be leveraged. So, your NoSQL stack might still leverage a relational database, say MySQL, but it’ll also contain Redis as a persistence lookup for specific parts of the system as well as Hadoop for your intensive data processing. Put simply, NoSQL is about being open and aware of alternative, existing and additional patterns and tools for managing your data.

You might be wondering where MongoDB fits into all of this. As a document-oriented database, Mongo is a more generalized NoSQL solution. It should be viewed as an alternative to relational databases. Like relational databases, it too can benefit from being paired with some of the more specialized NoSQL solutions.

Also, most web apps will use either a relational or document-oriented database. A number of Platform-as-a-Service (PaaS) providers allow you to use Go applications on their clouds but, as of this writing, only Heroku has a paid MongoDB add-on or we could use Heroku and access our MongoDB database on MongoLab (later on, we shall see how).

What’s MongoDB?

MongoDB is a high-performance, open source, schema-free, document-oriented database written in C++.

MongoDB Core Concepts

  • 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.

To recap, MongoDB is made up of databases which contain collections. A collection is made up of documents. Each document is made up of fields. Collections can be indexed, which improves lookup and sorting performance. Finally, when we get data from MongoDB we do so through a cursor whose actual execution is delayed until necessary.

MongoDB

MongoDB

Note: The core difference between relational databases and document-oriented databases comes from the fact that relational databases define columns at the table level whereas a document-oriented database defines its fields at the document level.

MongoLab - The Fully-managed MongoDB-as-a-Service

MongoLab 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. Next, click on “Plans & Features” and then select the free Sandbox plan.

Create a database

Next, create a free database. I have created a database named godata in my account. To connect using a driver via the standard URI:

1 mongodb://<dbuser>:<dbpassword>@ds031271.mongolab.com:31271/godata

Database User

A database user is required to connect to the above database. Click here to create a new one. I entered “IndianGuru” for a Database username (you can enter whatever you want) and the requisite password.

mgo

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

To install mgo, in a command window type:

1 go get gopkg.in/mgo.v2

JSON Recap

JSON stands for JavaScript Object Notation.

JSON is syntax for storing and exchanging text information, much like XML. JSON is smaller than XML, and faster and easier to parse. JSON is language independent. Here’s an example:

1 {
2 "employees": [
3 { "firstName":"John" , "lastName":"Doe" },
4 { "firstName":"Anna" , "lastName":"Smith" },
5 { "firstName":"Peter" , "lastName":"Jones" }
6 ]
7 }

The employee object is an array of 3 employee records (objects).

JSON data is written as name/value pairs. A name/value pair consists of a field name (in double quotes), followed by a colon and followed by a value:

1 "firstName" : "Satish"

JSON values can be:

  • A number (integer or floating point)
  • A string (in double quotes)
  • A Boolean (true or false)
  • An array (in square brackets)
  • An object (in curly brackets)
  • null

JSON objects are written inside curly brackets, Objects can contain multiple name/values pairs:

1 { "firstName":"Satish" , "lastName":"Talim" }

package mgo

Usage: import "gopkg.in/mgo.v2"

The mgo project (pronounced as “mango”) is a rich MongoDB driver for the Go language.

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

1 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:

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

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

Function Close

func (s *Session) Close()

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

Function SetSafe

func (s *Session) SetSafe(safe *Safe)

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:

1 session.SetSafe(&mgo.Safe{})
Function Safe

func (s *Session) Safe() (safe *Safe)

Safe returns the current safety mode for the session.

Function DB

func (s *Session) DB(name string) *Database

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.

Function C

func (db *Database) C(name string) *Collection

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

Function Insert

func (c *Collection) Insert(docs ...interface{}) error

Insert inserts one or more documents in the respective collection.

Function Find

func (c *Collection) Find(query interface{}) *Query

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.

Function One

func (q *Query) One(result interface{}) (err error)

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 by gobson. This function blocks until either a result is available or an error happens.

package bson

Usage: import "labix.org/v2/mgo/bson"

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

type M

type M map[string]interface{}

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}

Program mongohqconnect.go

The program mongohqconnect.go connects to our database godata hosted on MongoLab and creates a collection user.

Program mongohqconnect.go


 1 package main
 2  
 3 import (
 4         "fmt"
 5         "gopkg.in/mgo.v2"
 6         "gopkg.in/mgo.v2/bson"
 7         "log"
 8         "os"
 9 )
10  
11 type Person struct {
12         Name string
13         Email string
14 }
15  
16 func main() {
17         // Do the following:
18         // In a command window:
19         // set MONGOLAB_URL=mongodb://IndianGuru:password@ds031271.mongolab.com:31271/goda\
20 ta
21         // IndianGuru is my username, replace the same with yours. Type in your password.
22         uri := os.Getenv("MONGOLAB_URL")
23         if uri == "" {
24                 fmt.Println("no connection string provided")
25                 os.Exit(1)
26         }
27  
28         sess, err := mgo.Dial(uri)
29         if err != nil {
30                 fmt.Printf("Can't connect to mongo, go error %v\n", err)
31                 os.Exit(1)
32         }
33         defer sess.Close()
34         
35         sess.SetSafe(&mgo.Safe{})
36         
37         collection := sess.DB("godata").C("user")
38 
39         err = collection.Insert(&Person{"Stefan Klaste", "klaste@posteo.de"},
40 	                        &Person{"Nishant Modak", "modak.nishant@gmail.com"},
41 	                        &Person{"Prathamesh Sonpatki", "csonpatki@gmail.com"},
42 	                        &Person{"murtuza kutub", "murtuzafirst@gmail.com"},
43 	                        &Person{"aniket joshi", "joshianiket22@gmail.com"},
44 	                        &Person{"Michael de Silva", "michael@mwdesilva.com"},
45 	                        &Person{"Alejandro Cespedes Vicente", "cesal_vizar@hotmail.com"})
46         if err != nil {
47                 log.Fatal("Problem inserting data: ", err)
48                 return
49         }
50 
51         result := Person{}
52         err = collection.Find(bson.M{"name": "Prathamesh Sonpatki"}).One(&result)
53         if err != nil {
54                 log.Fatal("Error finding record: ", err)
55                 return
56         }
57 
58         fmt.Println("Email Id:", result.Email)
59 }

Deploying gomongohq.go on Heroku

In a previous free eBook, we have seen how to deploy web apps to Heroku.

The program gomongohq.go will reside on Heroku and will connect to our database godata hosted on MongoLab. The app will fetch information (the email id of say user Stefan Klaste) from the collection user.

Program gomongohq.go


  1 package main
  2 
  3 import (
  4 	"fmt"
  5 	"html/template"
  6         "labix.org/v2/mgo"
  7         "labix.org/v2/mgo/bson"
  8         "log"
  9 	"net/http"
 10 	"os"
 11 )
 12 
 13 type Person struct {
 14         Name string
 15         Email string
 16 }
 17 
 18 func main() {
 19 	http.HandleFunc("/", root)
 20         http.HandleFunc("/display", display)
 21         fmt.Println("listening...")
 22         err := http.ListenAndServe(GetPort(), nil)
 23         if err != nil {
 24                 log.Fatal("ListenAndServe: ", err)
 25                 return
 26         }
 27 }
 28 
 29 func root(w http.ResponseWriter, r *http.Request) {
 30 	fmt.Fprint(w, rootForm)
 31 }
 32 
 33 const rootForm = `
 34   <!DOCTYPE html>
 35     <html>
 36       <head>
 37         <meta charset="utf-8">
 38         <title>Your details</title>
 39         <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.4.2/pure-min.css">
 40       </head>
 41       <body style="margin: 20px;">
 42         <h2>A Fun Go App on Heroku to access MongoDB on MongoLab</h2>
 43         <p>This simple app will fetch the email id of a person, if it's already there in t\
 44 he MongoDB database.</p>
 45         <p>Please enter a name (example: Stefan Klaste)</p>
 46         <form action="/display" method="post" accept-charset="utf-8" class="pure-form">
 47           <input type="text" name="name" placeholder="name" />
 48           <input type="submit" value=".. and query database!" class="pure-button pure-butt\
 49 on-primary"/>
 50 	</form>
 51         <div>
 52           <p><b>&copy; 2015 Go Challenge. All rights reserved.</b></p>
 53         </div>	
 54       </body>
 55     </html>
 56 `
 57 
 58 var displayTemplate = template.Must(template.New("display").Parse(displayTemplateHTML))
 59 
 60 func display(w http.ResponseWriter, r *http.Request) {
 61         // In the open command window set the following for Heroku:
 62         // heroku config:set MONGOHQ_URL=mongodb://IndianGuru:password@troup.mongohq.com:1\
 63 0080/godata
 64         uri := os.Getenv("MONGOHQ_URL")
 65         if uri == "" {
 66                 fmt.Println("no connection string provided")
 67                 os.Exit(1)
 68         }
 69  
 70         sess, err := mgo.Dial(uri)
 71         if err != nil {
 72                 fmt.Printf("Can't connect to mongo, go error %v\n", err)
 73                 os.Exit(1)
 74         }
 75         defer sess.Close()
 76         
 77         sess.SetSafe(&mgo.Safe{})
 78         
 79         collection := sess.DB("godata").C("user")
 80 
 81         result := Person{}
 82 
 83         collection.Find(bson.M{"name": r.FormValue("name")}).One(&result)
 84 
 85         if result.Email != "" {
 86                 errn := displayTemplate.Execute(w, "The email id you wanted is: " + result\
 87 .Email)
 88                 if errn != nil {
 89                         http.Error(w, errn.Error(), http.StatusInternalServerError)
 90                 } 
 91         } else {
 92                 displayTemplate.Execute(w, "Sorry... The email id you wanted does not exis\
 93 t.")
 94         }
 95 }
 96 
 97 const displayTemplateHTML = ` 
 98 <!DOCTYPE html>
 99   <html>
100     <head>
101       <meta charset="utf-8">
102       <title>Results</title>
103       <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.4.2/pure-min.css">
104     </head>
105     <body>
106       <h2>A Fun Go App on Heroku to access MongoDB on MongoLab</h2>
107       <p><b>{{html .}}</b></p>
108       <p><a href="/">Start again!</a></p>
109       <div>
110         <p><b>&copy; 2015 Go Challenge. All rights reserved.</b></p>
111       </div>
112     </body>
113   </html>
114 `
115 
116 // Get the Port from the environment so we can run on Heroku
117 func GetPort() string {
118         var port = os.Getenv("PORT")
119 	// Set a default port if there is nothing in the environment
120 	if port == "" {
121 		port = "4747"
122 		fmt.Println("INFO: No PORT environment variable detected, defaulting to " + port)
123 	}
124 	return ":" + port
125 }

The program by now should be self explanatory.

  • The rootForm uses Pure a set of small, responsive CSS modules that you can use in every web project.
  • The function display uses the html/template package and the mgo driver to access the database on MongoLab. If the name is found in the database the function throws a page to the user with the email id for that name.

You can visit this app here.