SauceDB - Building the back end with IBM Bluemix

Welcome to another post detailing my efforts to build an Ionic-based mobile app backed by Node.js and Cloudant on IBM Bluemix. In my last post, I focused on the front end of the application. I talked about the various screens I built and how my service layer used mock data to generate data. In today's post, I'm going to setup, design, and connect a back end server to start replacing some of that mock data with real information. As a reminder, you can find the initial post in this series here,

Alright - so as I mentioned, I'm going to use IBM Bluemix to host the application. I've talked about Bluemix before, but let me give you a quick refresher on what it offers.

Bluemix-logo-right

Bluemix is a PaaS offering. If you aren't up to date on the latest acronyms the cool kids use, this is "Platform as a Service". In short, it lets you set up applications and services on the cloud all via a simple dashboard and command line. There's a variety of different services, some I've already blogged about here before, and a large library of code you can add to your application to make using those services even easier. I've already set up my application in Bluemix, but let me walk you through what the process was like.

After signing in (and you can sign up for a trial for free), I clicked on Create App:

shot1

Then selected Mobile:

shot2

And then the option for Hybrid:

shot3

I clicked continue on the detail and then named the app. Note that in the screen shot below I used a slightly different name so as to not conflict with the app I already created.

shot4

After you hit finish, your application is going to be staged and a number of default services assigned to it.

At this point, you have a few options. You can take an existing Node.js app and configure to work with Bluemix services, or you can take their boilerplate code and work with that. Since this is a new app, I recommend getting the boilerplate. You can find that by clicking "Start coding" in the left hand nav:

shot6

This leads you to a documentation page where you can download both the command line interface, something you'll only need to do once, and the boilerplate. As I said, I recommend getting the boilerplate as it has the required modules to work with Bluemix already defined in the package.json. It also includes some sample code that you'll probably want to remove. For example, I'm not using the Mobile Application Security service so I don't need the package. But it is a good place to start.

In case your curious, the "Enable Node.js app" you'll see on top...

shot5

is just another link to documentation that you can read later. Whenever I see this UI I assume that is a required link, but it is not. To be clear, your Bluemix app itself is ready to kick butt right away, you don't need to flip some toggle. You do need to upload your code using the cf command line. That "Start coding" link I just mentioned explains how to do that. It's fairly simple and I won't repeat it here.

For SauceDB, I needed to modify the services set up by Bluemix. Here is what you get by default:

shot7

Since I'm not using them, I removed both Mobile Application Security and Mobile Data. You can do that using the little gear icon in the upper right side of each service. To add in support for Cloudant, you simply click "Add a Service or API" and find the Cloudant service on the next page. As I said, Bluemix provides access to a bunch of services, so you can use the search form on top to filter them out:

shot8

Ok, so that was a lot, so let me quickly review. I use Bluemix to setup a new mobile application, gave it a new name, and then added a Cloudant service. I can now start building my Node.js app (again, using their boilerplate as a starting point), and then push to Bluemix when I want to deploy. For everything I've done with SauceDB so far, I have yet to actually push my app to Bluemix, which is good because I can work much quicker locally during development.

So, enough with the consoles and crap, let's talk code. First off, everything I'm showing below is available up on the Github project: https://github.com/cfjedimaster/SauceDB.

One of the first things I did was look for a npm module for cloudant. Here's the one I used: https://www.npmjs.com/package/cloudant. After adding it to my package.json and installing, I added the library to my app.js code and pointed to a database.

var cloudant = require('cloudant')(credentials.cloudant_access_url);
var db = cloudant.use("sauces");

The URL value you see there, which comes from a credentials.json file that will not be in Github, comes from the service description in the Bluemix console. You can see it by simply clicking the "Show credentials" link:

shot9

Alright... so I'm connecting to a database called sauces, which doesn't actually exist yet. If you click the Cloudant service ... um... "box" I suppose, the detail page will include a link to launch the Cloudant console:

shot10

At this point, you can use the "Add New Database" button to add a new database. I did this for "sauces":

shot11

My goal now was to create some data so that my Node.js app could pick it up and send it back to the mobile app. If you remember the last post, my application begins with a "feed" of reviews. Every review is made by a user and involves a sauce. Cloudant lets you create new docs by hand, so I built a sauce object to start off with:

{
  "_id": "786fe8d99eaf2c7e5927afe9d2afe44c",
  "name": "Sauce 1",
  "company": "Company 1",
  "reviews": [
    {
      "posted": "2014/05/21 10:00:00",
      "rating": 2,
      "text": "This was a bad sauce.",
      "user": {
        "name": "Joe",
        "img": "http://placekitten.com/g/40/40"
      }
    },
    {
      "posted": "2014/12/21 10:00:00",
      "rating": 2,
      "text": "December This was a bad sauce.",
      "user": {
        "name": "Joe",
        "img": "http://placekitten.com/g/40/40"
      }
    },
    {
      "posted": "2015/6/1 10:00:00",
      "rating": 2,
      "text": "NO IM MORE RECENT This was a bad sauce.",
      "user": {
        "name": "Joe",
        "img": "http://placekitten.com/g/40/40"
      }
    }
  ]
}

User's should probably be a look up reference, but I just wanted some sample data. This data represents one sauce with three reviews. I created a second sauce with one review. At this point I realized I had a problem. It isn't difficult to get all the documents in a database. But that's not what I want. In my case, I need the reviews from the objects, and I need to sort them. I spent a good few hours in the Cloudant docs, and I discovered that a "View" could do what I needed. I'm not going to pretend that this is the best answer, but it seemed to work well.

I set up a new design document, and using the Cloudant dashboard, I was able to both write my view and test it interactively, which was a big help. Here's the code I used:

function(doc) {
  if(doc.reviews.length === 0) return;
  for(var i=0;i<doc.reviews.length;i++) {
      var review = doc.reviews[i];
      emit(review.posted, {review:review,sauce_name:doc.name,sauce_company:doc.company});
  }
}

And here it is in the dashboard with the output to the right.

shot12

What's cool is - using the Cloudant npm library, it was incredibly trivial to get this view:

db.view("Reviews", "reviews", {descending:true}, function(err, body) {

That's simple as heck - but there's one more aspect to this whole thing that I needed to setup. One of the features you can use with your hybrid mobile application is called Cloud Code. While it offers a couple features, in general, you can think of it as way to "short cut" your calls to your Node.js application. Using this requires a few changes in your hybrid application. Obviously you have to get the JavaScript library and include it in your HTML.

Next, you need to initialize Bluemix from within your code. Mine is in app.js, inside $ionicPlatform.ready:

.run(function($ionicPlatform) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if(window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if(window.StatusBar) {
      StatusBar.styleDefault();
    }
    
    var config = {
      applicationId:'38a0a550-b018-4a10-b879-aec68868c249',
      applicationRoute:'http://saucedb.mybluemix.net',
      applicationSecret:'735c7d0de828ab956bae772e996a55620676ff05'
    };
    
    IBMBluemix.initialize(config).then(function() {
      console.log('ok maybe?');
    }, function(err) {
       console.log('crap your pants time');
    });
    
    
  });
})

Obviously I'm not actually handling the error case here, but it's something I'll worry about later. (Famous last words.) That sets up basic configuration - and this is where Cloud Code comes in. I can hit my Node.js app with the following code:

var cc = IBMCloudCode.initializeService();
cc.get("/feed").then(function(data){
    data = JSON.parse(data);
},function(err){
    console.log(err);
});

Note how I just request /feed and nothing more. Because of the earlier configuration code where I specified my application tokens, the Bluemix code knows how to route my requests to my Node.js application on Bluemix. But I just said I was running locally - so how do I fix that?

var cc = IBMCloudCode.initializeService();
cc.setBaseUrl('http://localhost:3000');
cc.get("/feed").then(function(data){
    data = JSON.parse(data);
},function(err){
    console.log(err);
});

Here is the actual full call now in my services.js file:

var getFeed = function() {
    var deferred = $q.defer();
    
    //fake it till we make it
    var feed = [];

        //now try the app
        var cc = IBMCloudCode.initializeService();
        cc.setBaseUrl('http://localhost:3000');
        cc.get("/feed").then(function(data){
            data = JSON.parse(data);
            for(var i=0;i<data.length;i++) {
                var result = data[i];
                console.log('did i run');
                var item = {
                    id:result.id,
                    posted:result.review.posted,
                    sauce:{
                        name:result.sauce_name,
                        company:result.sauce_company
                    },
                    rating:result.review.rating,
                    avgrating:0,
                    text:result.review.text,
                    user:{
                        img:result.review.user.img,
                        name:result.review.user.name
                    }
                };
                feed.push(item);
            }
            console.log('sending '+feed);
            deferred.resolve(feed);
            
        },function(err){
            console.log(err);
        });

    
    return deferred.promise;
}

On the server side, I have to slightly modify how I create my route. Instead of just app.get('....'), I do something slightly different:

var ibmconfig = ibmbluemix.getConfig();
app.get(ibmconfig.getContextRoot()+'/feed',  function(req, res) {
    console.log('Requesting feed');

    db.view("Reviews", "reviews", {descending:true}, function(err, body) {
        //stuff cut out here to keep the code snippet simpler
    });
});

I get an instance of the ibmbluemix config object and grab the context root. This essentially creates a special path that is coordinated (may not be the best word) with the mobile side.

Whew! That was a lot. Did it feel like a lot to you? To be fair, this post covered a lot of setup. We got a server up and running on Bluemix (technically all we did was provision it and we're running code locally), we set up Cloudant, and we created an API to fetch feed data and display it in the mobile app. The end result is real, ok, hand-written essentially, data being driven by Cloudant:

iOS Simulator Screen Shot Jul 20, 2015, 2.33.22 PM

Remember, you can see all the source code here: https://github.com/cfjedimaster/SauceDB

That's it for now. In the next post, I'll continue to flesh out the views and start writing data from the mobile app back to the server.

Like This?

If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can also subscribe to the email feed to get notified of new posts.

See Also