Posted by virantha on Sun 29 September 2013

Quick web app with Go, Ember.js, and MongoDB

skip_better Screenshot

As part of my recent foray into the Go language, I stumbled across this excellent example by Nerdyworm part 1, part 2, part 3 that shows how to build a small web application using the ember.js javascript framework and Go providing the backend server. I took this example, fixed up some incompatibilites with the latest version of ember-data.js, and added support for a backing store using MongoDB. You can find all the code for this at my github, while I describe some of the more interesting stuff below.

1   Getting started

1.1   Get my code

git clone https://github.com/virantha/kittens_ember_go.git
cd kittens_ember_go
setenv GOPATH `pwd`/go/src

1.2   Depedencies

I'm assuming you have Go already installed:

1.2.1   MongoDB

Follow the instructions at Mongo to install for your platform. Make sure you can start the Mongo server afterwards and insert some records into a test database to validate your install.

Then, you need to install the Go driver for Mongodb. First, you need to get Canonical's Bazaar revision control system. Make sure you can type ''bzr'' at the command-line to verify install. Then, run the following command to install Mgo

# Install mgo, the mongodb driver for go
# Make sure your GOPATH is set as above
go get labix.org/v2/mgo

1.2.2   Gorilla

Install this muxing package for Go:

go get github.com/gorilla/mux

2   What we're going to be building

We'll build a small web application that you can open directly in your browser. It will do exactly what Nerdyworm built in his original, except that this version adds data persistence in a database. The app presents the following interface:

Screenshot of kittens app

It will let you create a "kitten", linking to a random kitten image.  You can then either edit the kitten name, or delete the kitten, with all kittens stored in the backing MongoDB database.  The web interface is done in Ember.js, and Go provides the web-server/database access.

2.1   Running the app

Once you have everything installed, you can just do the following to start
the server:
cd site
go run ../go/src/github.com/virantha/server_mongo.go

Then navigate to http://localhost:8081

3   Code discussion

First, I'll discuss the server-side Go program that accesses the Mongo database and provides JSON objects for the front-end. That's not to say the client-side is not interesting; quite the contrary, it's pretty impressive what one can do with ember.js, but most of that has already been covered in Nerdyworm's posts.

3.1   The Backend Go Server (server_mongo.go)

The server is just one single go file; here's the list of imports:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main
import (
  "net/http"
  "log"
  "github.com/gorilla/mux"
  "encoding/json"
  "math/rand"
  "fmt"
  "labix.org/v2/mgo"
  "labix.org/v2/mgo/bson"
)

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

Lines 6 to 12 setup the routes for "getting" kittens for display, "creating" a new kitten, "updating" an existing kitten, and "deleting" a kitten. This is all unchanged from the original source code.

Lines 16 to 20 are new, and handle the MongoDB connection. We open a new session and then connect to the kittens collection. These are global variables for this app, explained in the next section.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func main() {
    var err error

    log.Println("Starting Server")

    r := mux.NewRouter()
    r.HandleFunc("/api/kittens", KittensHandler).Methods("GET")
    r.HandleFunc("/api/kittens", CreateKittenHandler).Methods("POST")
    r.HandleFunc("/api/kittens/{id}", UpdateKittenHandler).Methods("PUT")
    r.HandleFunc("/api/kittens/{id}", DeleteKittenHandler).Methods("DELETE")
    http.Handle("/api/", r)
    http.Handle("/", http.FileServer(http.Dir(".")))

    log.Println("Starting mongo db session")

    session, err = mgo.Dial("localhost")
    if err != nil { panic (err) }
    defer session.Close()
    session.SetMode(mgo.Monotonic, true)
    collection = session.DB("Kittens").C("kittens")

    log.Println("Listening on 8080")
    http.ListenAndServe(":8080", nil)
}

3.1.2   Globals and types

Here's the list of custom types defined for our application. First, we have the database pointers to the session and collection.

Then we have the struct for a single Kitten, which contains an Id of type ObjectId (used as unique identifier in the MongoDB), the name of the kitten, and a string with the URL for the picture. Note the string literals (the backticks are just to avoid backslashing the double-quotes) at the end of each member type that specifies a tag for each member that will be used as a name for JSON or BSON representations instead of the member name. Also note how we can use _id for the BSON representation for mongo (which is the default unique identifier in Mongo for each element), while still keeping id for the JSON, and capitalized Id for making it an exported member in Go.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var (
    session *mgo.Session
    collection *mgo.Collection
)

type Kitten struct {
    Id bson.ObjectId `bson:"_id" json:"id"`
    Name string `json:"name"`
    Picture string `json:"picture"`
}

type KittenJSON struct {
    Kitten Kitten `json:"kitten"`
}


type KittensJSON struct {
    Kittens []Kitten `json:"kittens"`
}

3.1.3   The handlers

We have a set of 4 handlers for each of the actions the client-side application might send our way:

3.1.3.1   Create a kitten

In lines 5 to 9, we decode the incoming kitten from JSON. Next, in lines 10 to 15, we pick a random image width/height and to get the corresponding kitten image from placekitten.com.

Finally, in linees 19 to 26, we create a new Id, and insert this kitten instance into our database, after which we return a JSON object of the kitten to render in the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func CreateKittenHandler(w http.ResponseWriter, r *http.Request) {

    var kittenJSON KittenJSON

    err := json.NewDecoder(r.Body).Decode(&kittenJSON)
    if err != nil { panic(err) }

    kitten := kittenJSON.Kitten

    // Generate a random dimension for the kitten
    width := rand.Int() % 400
    height := rand.Int() % 400
    if width < 100 { width += 100 }
    if height < 100 { height += 100}
    kitten.Picture = fmt.Sprintf("http://placekitten.com/%d/%d", width, height)

    // Store the new kitten in the database
    // First, let's get a new id
    obj_id := bson.NewObjectId()
    kitten.Id = obj_id

    err = collection.Insert(&kitten)
    if err != nil {
        panic(err)
    } else {
        log.Printf("Inserted new kitten %s with name %s", kitten.Id, kitten.Name)
    }

    j, err := json.Marshal(KittenJSON{Kitten: kitten})
    if err != nil { panic(err) }
    w.Header().Set("Content-Type", "application/json")
    w.Write(j)
}
3.1.3.2   Display the kittens

Here, we just return an array of kitten objects from the database for display in the client. We iterate through the kittens collection and build up a local slice of kittens (I admit, this is pretty inefficient and we'd probably want to cache this in a real application) that then gets marshaled into JSON for display.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func KittensHandler(w http.ResponseWriter, r *http.Request) {

    // Let's build up the kittens slice
    var mykittens []Kitten

    iter := collection.Find(nil).Iter()
    result := Kitten{}
    for iter.Next(&result) {
        mykittens = append(mykittens, result)
    }

    w.Header().Set("Content-Type", "application/json")
    j, err := json.Marshal(KittensJSON{Kittens: mykittens})
    if err != nil { panic (err) }
    w.Write(j)
    log.Println("Provided json")

}
3.1.3.3   Delete a kitten

This is probably the most trivial to write. The only "gotcha" (if you can even call it that) is to convert the incoming string id into an actual ObjectId object for lookup in the MongoDB.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func DeleteKittenHandler(w http.ResponseWriter, r *http.Request) {
    // Grab the kitten's id from the incoming url
    var err error
    vars := mux.Vars(r)
    id := vars["id"]

    // Remove it from database
    err = collection.Remove(bson.M{"_id":bson.ObjectIdHex(id)})
    if err != nil { log.Printf("Could not find kitten %s to delete", id)}
    w.WriteHeader(http.StatusNoContent)
}
3.1.3.4   Update a kitten name

Again, pretty straightforward:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
func UpdateKittenHandler(w http.ResponseWriter, r *http.Request) {
    var err error
    // Grab the kitten's id from the incoming url
    vars := mux.Vars(r)
    id := bson.ObjectIdHex(vars["id"])

    // Decode the incoming kitten json
    var kittenJSON KittenJSON
    err = json.NewDecoder(r.Body).Decode(&kittenJSON)
    if err != nil {panic(err)}

    // Update the database
    err = collection.Update(bson.M{"_id":id},
             bson.M{"name":kittenJSON.Kitten.Name,
                    "_id": id,
                    "picture": kittenJSON.Kitten.Picture,
                    })
    if err == nil {
        log.Printf("Updated kitten %s name to %s", id, kittenJSON.Kitten.Name)
    } else { panic(err) }
    w.WriteHeader(http.StatusNoContent)
}

And that's pretty much it for the back-end server. Go is pretty concise!

3.2   Frontend ember.js app

3.2.1   HTML output template

First, here's the html that the ember framework will write into. A lot of it is just boilerplate and there's nothing really new here from the original source that's been discussed by nerdyworm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<html>
<head>
  <meta charset="utf-8">
  <title>Kittens</title>
      <link rel="stylesheet" href="css/normalize.css">
      <link rel="stylesheet" href="css/style.css">
      <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.no-icons.min.css" rel="stylesheet">
</head>
</head>
<body>

<script type="text/x-handlebars">
<div class="container">
    <h1>Create your own kitten</h1>
    <ul class="nav nav-tabs">
        <li> {{#linkTo 'index'}}Index{{/linkTo}}</li>
        <li> {{#linkTo 'create'}}Create{{/linkTo}}</li>
    </ul>
  {{outlet}}
</div>
</script>

<script type="text/x-handlebars" data-template-name="index">
<ul class="thumbnails">
    {{#each controller}}
        <li class="span3">
        <div class="thumbnail">
          <img {{bindAttr src="picture"}}/>
          <div class="caption">
              <h3>{{name}}</h3>
              {{#linkTo 'edit' this}}Edit{{/linkTo}}
              <button {{action deleteKitten this}}>Delete</button>
          </div>
        </div>
        </li>
    {{/each}}
</ul>
</script>

<script type="text/x-handlebars" data-template-name="_form">
    <form {{action save on="submit"}} class="form-inline">
        {{input type="text" value=name}}
        <button type="submit" class="btn btn-primary">Save</button>
    </form>
</script>

<script type="text/x-handlebars" data-template-name="create">
    <h1>Create kitten</h1>
    {{partial 'form'}}
</script>

<script type="text/x-handlebars" data-template-name="edit">
    <h1>Edit kitten</h1>
    {{partial 'form'}}
</script>

<script src="js/lib/jquery.js"></script>
<script src="js/lib/handlebars.js"></script>
<script src="js/lib/ember.js"></script>
<script src="js/lib/ember-data.js"></script>
<script src="js/app_kittens.js"></script>
</body>
</html>

3.2.2   Ember.js application

Now, here is the actual client-side logic that's been updated to work with the latest version of ember and ember-data:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
(function() {
    App = Ember.Application.create();

    App.Store = DS.Store.extend({
      revision: 12,
    });


    App.Kitten = DS.Model.extend({
        name: DS.attr('string'),
        picture: DS.attr('string'),
    });

    App.KittenAdapter = DS.RESTAdapter.extend({
        namespace: 'api'
    });

    App.IndexRoute = Ember.Route.extend({
        model: function() {
            return this.store.find('kitten');
        },

        actions: {
            deleteKitten: function(kitten) {
                kitten.deleteRecord();
                kitten.save();
            }
        }
    });

    // Add some routes
    App.Router.map(function() {
        this.route('create');
        this.route('edit', {path: '/edit/:kitten_id'});
    });

    // Add a controller to dispatch create commands
    App.CreateController = Ember.Controller.extend( {
        name: null,
        actions: {
            save:function() {
                var kitten =this.store.createRecord('kitten');
                kitten.set('name', this.get('name'));
                kitten.save().then(function() {
                    this.transitionToRoute('index');
                    this.set('name', '');
                }.bind(this));
            }
        }
    });
    App.EditController = Ember.ObjectController.extend({
        actions: {
            save: function() {
                var kitten = this.get('model');
                kitten.save().then(function() {
                    this.transitionToRoute('index');
                }.bind(this));
            }
        }
    });

})();

© Virantha Ekanayake. Built using Pelican. Modified svbhack theme, based on theme by Carey Metcalfe