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
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:
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)); } } }); })(); |