Mobile GeoLocation App in 30 minutes – Part 3: Deploying to OpenShift

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.

About these ads

25 comments on “Mobile GeoLocation App in 30 minutes – Part 3: Deploying to OpenShift

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

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

      • 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

  2. 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?

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

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

  4. 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) + ‘});’);
    })
    }

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

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

  7. Hi, thanks for this tutorial. I’m trying to add a container above the map to show some details, but I’m not having success :( I turned the panel into a container and add a “ContainerInitialize” like this:

    onContainerInitialize: function(component, options) {
    var detailPanel = component.down(‘#detail’);
    var data = this.getRec().data;
    data.petTracker = this.getPetTracker();
    detailPanel.setData(data);
    var rec = this.getRec();
    }

    Maybe it’s contending with the main controller. If you want I will post a more complete code. Thanks again.

    • That sounds like a strange way to do that when you can simply create a custom container view the way you want it. I do something like this in my own app:


      Ext.define('App.view.checkin.Card', {
      extend: 'Ext.Container',
      xtype: 'checkin',
      config: {
      cls: 'checkin-map',
      layout: 'vbox',
      fullscreen: true,
      hidden: true,
      items: [
      {
      xtype: 'map',
      itemId: 'checkinMap',
      flex: 3,
      autoUpdate: false,
      mapOptions: {
      zoom: 15,
      center: new google.maps.LatLng(32,-112),
      zoomControl: true,
      panControl: false,
      mapTypeControl: false,
      streetViewControl: false,
      mapTypeId: google.maps.MapTypeId.ROADMAP
      }
      },
      {
      xtype: 'buildings', // list of buildings view below map
      flex: 5
      }
      ]
      }
      });

  8. Yes, but if I want a container that displays for half some details and the other half the map?

    config: {
    layout: ‘vbox’,
    items: [
    {
    xtype: 'toolbar',
    docked: 'top',
    itemId: 'mapToolbar',
    title: 'DogTag',
    items: [
    {
    xtype: 'button',
    ui: 'back',
    itemId: 'backButton',
    text: 'Back'
    },
    {
    xtype: 'spacer'
    },
    {
    xtype: 'button',
    itemId: 'nearButton',
    text: 'Near Me'
    }
    ]
    },
    {
    xtype: ‘container’,
    flex: 1,
    tpl: [
    '',
    '{name}',
    '{description}',
    '{coords}',
    ''
    ]
    }
    ]…….
    ………………………..

    Apparently this is not enough, what should I do to retrieve items from the store to place them in the second container? I tried to create a new variable in the controller in the “onPetSelected” function that refers to that container…but don’t work fine :-/

  9. Just use flex. Flex them to equal values for each container on the view to have 50% each. Regarding the store, you need to create another view, define it as an xtype and bind it to a store. You see in my example I have an xtype of buildings? That’s how I get a child reference into my overall container. Something like:

    Ext.define('People.view.checkin.List', {
        extend: 'Ext.List',
        xtype: 'buildings',
        config: {
            itemId: 'BuildingList',
            cls: 'building-list',
            itemHeight: 50,
            pressedDelay: 0,
            variableHeights: false,
            emptyText: "
    No Buildings Found
    ", loadingText: "Loading...", onItemDisclosure: false, store: 'Buildings', singleSelect: true, itemTpl: [ "
    ", "
      ",Building {ID}" ]...

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