Overview
In the previous two parts, you saw how to create a Node.js application and store JSON data in MongoDB (Part 1). You also saw how to present this data in a mobile Web application using Sencha Touch complete with Google Maps and geospatial searching (Part 2). Now, we are going to take both of these two pieces and deploy them on a Platform as a Service (PaaS). I looked at three different provides in this space: OpenShift, Heroku and Nodejitsu. The latter is Node.js specific while the other two you can deploy other types of applications (Java EE, Ruby, PHP, Python, etc.). I really liked RedHat’s OpenShift model. It has several nice dev features that I thought worked well which I will go over in this post. Nodejitsu worked well too but didn’t use Git (a distributed SCM tool) to manage changes. Heroku required a credit card at the point I wanted to introduce MongoDB, so I opted not to continue with Heroku. It doesn’t hurt to experiment with multiple vendors since you can sign-up for free accounts on the respective sites.
One of the main motivations to use a PaaS is that it wouldn’t be very maintainable or production worthy SSH’ing to a server and running your node process every time you wanted to launch your app. Moreover, even if you run your process, it eventually will shutdown when you close your SSH session. There is an npm package forever that can keep this from happening, but I think running Node on a PaaS is ideal.
Using OpenShift to create a Node App
The first thing you’ll want to do is create a free OpenShift account here. Once you have an account created, you can create the application directly from the website (very convenient) or you can install Ruby and the RHC gem (sudo gem install rhc) that allows you to do the same thing from command line. The website is pretty intuitive so I won’t cover this and should you want to use the CLI, the set-up is sufficiently covered here. Note that there are a few prerequisites to use the CLI, namely installed Ruby and Msysgit. Be sure to have those installed as per the provided link before continuing.
For the purposes of deploying the app we created in Part 1, you would do the following after you’ve established your OpenShift domain and added your SSH public keys to commit code from Git:
First, we create “dogtag” as a nodejs app:
$ rhc app create -a dogtag -t nodejs-0.6
Second, we add the MongoDB cartridge:
$ rhc app cartridge add -a myMongo -c mongodb-2.0
Last, we add the RockMongo cartridge to administer MongoDB from the browser (context URI: /rockmongo from your domain)
$ rhc app cartridge add -a myMongo -c rockmongo-1.1
Working with your application in OpenShift
After you have created the app, the next part is copy the DogTag Node.js code to the “dogtag” directory that was created by OpenShift. Since Git is used to manage changes, you can run the following commands to push the code to the server:
$ git add .
$ git commit -am ‘Initial check-in’
$ git push
What will happen then is that the code changes will push to the server, stop the node.js instance, deploy your code and restart the server…very cool! It will pick-up any NPM packages you need as long as you define them in the package.json file (in our case, we need express and mongoose). After that you can go to the provided URL from OpenShift and test the various URL routes we defined in Part 1 (e.g., http://nodetest-loutilities.rhcloud.com/dogtag). You’ll notice that there is no data, so I created a JSON file that you can import into MongoDB to have some initial data. To do the import, you need to run the following command when you SSH to your server’s node:
$ mongoimport –host $OPENSHIFT_NOSQL_DB_HOST –port $OPENSHIFT_NOSQL_DB_PORT –username $OPENSHIFT_NOSQL_DB_USERNAME –password $OPENSHIFT_NOSQL_DB_PASSWORD –db dogtag –collection dogtags –file dogs.json –jsonArray
Note the $OPENSHIFT* provided constants so that you don’t have to figure out the actual host, port, username and password you are on which is especially helpful in a cloud environment running on multiple nodes. Once you import, you can check the files and add a geospatial index, which is required for the geospatial queries as follows:
$ mongo
> use dogtag
> db.dogtags.find() //will return results
> db.dogtags.find().forEach( function (e) { e.coords = [ e.longitude, e.latitude ]; db.dogtags.save(e); }); //will add the required format for a coordinates field to index
> db.dogtags.ensureIndex( { coords : “2d” } ) //indexes the coords field for geospatial queries
> exit
Now, if you go to the URL again you will have JSON data returned in a JSONP callback. You can change the callback simply by adding &callback=[your callback name]. This is necessary to prevent cross-site scripting issues and required when using the JSONP proxy in Sencha Touch.
Deploying the Mobile App
Since the Mobile App was created with Sencha Touch, I just used a “DIY” (Do it yourself) app from OpenShift to deploy my app there. The process is very similar:
$ rhc app create -a dogtags -t diy-0.1
This creates a very basic Web app. You can cd to the “diy” directory that is created and copy the code from the Mobile app there. It will prompt you to replace index.html, which is fine. There is one thing you need to do in the existing code; you will need to update the store’s proxy to point to your “dogtag” node.js app you created in the previous section as well as the Pettracker controller which also has these references. To do that, simply update the URL with the one OpenShift provides and append “dogtag” on the end to get the list as follows:
proxy: {
type: 'jsonp',
url: 'http://nodetest-loutilities.rhcloud.com/dogtag',
reader: {
type: 'json',
idProperty: '_id',
rootProperty: 'records',
useSimpleAccessors: true
}
}
Lastly, use git to push the updates to the server. It will deploy the new code and restart the server. Launch your app from a Webkit browser or from your mobile device and you should be in business similar to what you see here.
Once the app is deployed to OpenShift, you can tail the logs by running the following:
$ rhc app tail -a <app-name>
You can also start and stop the server as follows:
$ rhc app [start | stop ] -a <app-name>
And to snapshot a particular revision and go back to it you run the following:
$ rhc app snapshot save -a <app-name>
$ rhc app restore -a <app-name> -f <path to snapshot file>
Conclusion
I hope you found this three part series useful. In addition to creating a Node.js app and front-ending it with a Mobile application, you saw how easy it is to deploy such an application to the cloud.
What a great series of articles!! Were you at the MongoSD evening event? I just put it on twitter. Do you have a twitter handle I can reference.
Thanks Steve! Yes I was at the event…we met briefly, so you helped inspire me a bit on this last part!
I’m @occasl.
Hey Lou:
My only suggestion for the sencha app is to use a PHP cartridge rather than a DIY – it is a better choice for simple html + JS apps
Ok, I’ll do that for future, but I just thought PHP would introduce more overhead since I wasn’t using it.
I can do a mongo – db.dogtag.find() and get a list of results just not sure the geospatial index has worked for me. thanks
There is a little overhead but nothing to be noticeable. The other benefit is that a PHP app can take advantage of auto-scaling while a DIY can not.
Lou – I have been working on this for a bit and almost have it I think – so I load the Long Lat commands against mongo and the last 2 lines about coords and ensureIndex return nothing on screen so I assume they are fine. I then do not understand the callback sencha line after that. Sencha by default does callback=callback so only if we individually need this callback to =something else or is there a code change needed. As a result I do not have a working app yet – see http://turbin-fop.rhcloud.com – any help would be FANTASTIC thanks
Can you run a Mongo near query against the data from the mongo cli? Which line # are you referring to regarding the callback?
Not sure of how to run a mongo near query and pointers?
Callback relates to above instructions in the paragraph just above “Deploying the Mobile App”
Thanks again for help
The callback just refers to the name of the method you want your callback to be called. ExtJS JSONP proxy manages that for you, but you can define it in the URL like this if you wanted:
http://nodetest-loutilities.rhcloud.com/dogtag?callback=callback001
A near query would be:
db.dogtags.find( { coord: { $near : [lat,long] } } )
But keep in mind, I hard-coded the values near my location for the import so you’d have to replace those with ones near yours if you want near to work.
Hope that helps.
Thanks the near query I can work on later -just want to get demo working to see how it works with openshift. So I can not seam to get the database to fill the Chrome browser – mongo says it see it – must be the geospatial index code not connecting? any clues I can look at/for? maybe a test I can do console.log?
Still stumped – so here is URL http://turbin-fop.rhcloud.com/
SSH data returns this when doing db.name.find()
“name” : “So-Sue-Me”, “longitude” : -117.2052, “description” : “German Shepard”, “coords” : [ -117.2052, 32.9044 ] }
{ “_id” : ObjectId(“504f6f33ad630440c1b04ace”), “latitude” : 32.844, “date” : ISODate(“1970-01-16T13:09:07.200Z”),
So it looks like DB is fine with coords in place right? so for some reason the data is not populating – any ideas? thanks in advance.
Your node service is not returning any records: http://fop-fop.rhcloud.com/fop?callback=key0001. You need to debug that piece of the stack to find the problem.
ok thanks spent afternoon looking into this – and frustration setting in – can you give me a better clue – file name – lines to look at thanks
ok SMILES here – wow an adventure
server.js file line 31 – at end you have to put /yourdatabasename
// Establish connection to MongoDB
mongoose.connect(‘mongodb://’+dbuname+’:'+dbpwd+’@'+dbhost+’:'+dbport+’/fop’);
Hope this helps others.
if your console log on chrome I still see a few errors – any ideas on how to correct?
Exception:TypeError: Object # has no method ‘CallJSFunction’ chrome-extension://jpfgjjhcgfbfkkoelpepohanhmbhdanh/websiteLogon.js:1234
websiteLogon.callToFIDOTokenCounter chrome-extension://jpfgjjhcgfbfkkoelpepohanhmbhdanh/websiteLogon.js:1234
websiteLogon.initialize chrome-extension://jpfgjjhcgfbfkkoelpepohanhmbhdanh/websiteLogon.js:32
(anonymous function)
More info FOP is the DB FOPS is the collection – below is what I see for callbacks, anything weird?
exports.list = function(req, res) {
WhatIf.find(function(err, fop) {
res.setHeader(‘Content-Type’, ‘text/javascript;charset=UTF-8′);
res.send(req.query["callback"] + ‘({“records”:’ + JSON.stringify(fop) + ‘});’);
});
}
exports.show = (function(req, res) {
WhatIf.findOne({name: req.params.name}, function(error, fop) {
res.send([{Dog: fop}]);
})
});
exports.near = function(req, res) {
WhatIf.find({coords : { $near : [req.params.lon, req.params.lat], $maxDistance : req.params.dist/37.70}}, function (error, fop) {
res.setHeader(‘Content-Type’, ‘text/javascript;charset=UTF-8′);
res.send(req.query["callback"] +’({“records”:’ + JSON.stringify(fop) + ‘});’);
})
}
Thanks for the helpful post! I was wondering if you would mind explaining how you could tweak this so the device watch’s your position? That is so it updates as you walk along? I’m not sure what part of the code should be responsible for this or what I should change.
Thanks for the comment! I think what you’re looking for is watchPosition() which will periodically go get the updated position of the phone. A quick search I found this which might be of interest: http://google-maps-utility-library-v3.googlecode.com/svn/trunk/geolocationmarker/docs/reference.html. Good luck!
Thanks for responding – I had found watchPosition() but I haven’t been able to figure out yet where or how to successfully integrate it into your tutorial. I think that maybe I have to modify something in “initialize” in MapPanel but I couldn’t get it to start or maybe it’s in the controller but to do so seems to cascade changes throughout the code that are beyond my recognition. I feel like it should be a quick one liner somewhere (since it is with regular ol’ HTML5) but which line?!
Using the library above, you can definitely track (I’m using it for a real world application). Just instantiate it with your map object. In the code I provided, that would be renderMap() function in the controller.