Working with the New API - Part 2: Node.js

In the last part, we used Go to create a command line utility that reported on your database versions and upgrade options. In this part, we'll switch to Node.js and show how you can create customized web-based front ends for your MongoHQ deployments.

Say you wanted the ability to quickly browse through and view the statistics and backup status of all your databases. More importantly, you want to allow your operators to do that without giving them full access to the MongoHQ dashboard. Well, that's what we'll start putting together here.

Let's recap what we need to access the API. Any access needs to be authorized and to get authorised you need a personal access token for your application to use. This can be generated from your MongoHQ dashboard. You'll also want a library to help out with making REST API calls and that's all you really need. For our example app, we'll be using the node-rest-client package, which we can install with npm install node-rest-client and for the local web front end we'll be using Express 4 and Handlebars via the express3-handlebars package - we can install those with npm install express express3-handlebars and we are good to go.

First up in creating our app.js, we want to pull in the libraries we'll be using – Express, Express3-Handlebars, Node-REST-Client and Util:

var express = require("express");  
var exphbs = require("express3-handlebars");  
var util = require('util');  
var Client = require('node-rest-client').Client;  

And we'll want to get our personal access token. For this example, we're not hard coding it in but retrieving it from a file called accesstoken.txt. You'll want to create that and copy just your token into that file. That'll then be read in the main application here:

var fs=require('fs');  
var accesstoken=fs.readFileSync("./accesstoken.txt");  

To access the API, we'll need to make a note of the base URL of the various endpoints but more importantly we need to set up the headers for our HTTP requests so they request the right content type, use the right version of the API and present their credentials. The Node-REST-Client library can take a headers variable as part of its HTTP arguments, so we'll assemble that next:

var baseURL = "https://beta-api.mongohq.com";

var headers = {  
  "Content-Type": "application-json",
  "Accept-Version": "2014-06",
  "Authorization": "Bearer " + accesstoken
}
var httpArgs = {  
  "headers": headers
}

Now we just need to set up the REST client, create the Express web server and set Handlebars up to render pages for us:

var client = new Client();  
var app = express();  
var server = require("http").createServer(app);  
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));  
app.set('view engine', 'handlebars');  

We are now able to handle a request and turn it into a MongoHQ API call. When a user accesses the server's root, we want to present them with a list of accounts they can access. In Express, we can wrap that functionality as:

app.get('/', function(req, res) {  

We now want to make our request to the API. The List accounts endpoint, /accounts can provide that data: using the REST client, we can do an HTTP GET, passing the HTTP arguments we prepared earlier. When we get a response from the API query, the REST client will call back, with the response's body parsed as a JSON object and with the raw response:

  client.get(util.format("%s/accounts", baseURL), httpArgs,function(accounts,response) {

We are using util.format() to create our URLs here. Though in this case it would be shorter to just concatenate the baseURL and "/accounts" as we move on to endpoints with more parameters we will find it easier to manage the URL's template and parameters.

The first thing we will need to do in the callback is check that the request was handled properly. If the status code from the response is not 200 (Ok), then we will need to do some error reporting:

    if (response.statusCode != 200) {
      if (accounts.hasOwnProperty("error")) {
res.send(accounts.error);  
      } else {
res.send(response.headers.status);  
      }
      return;
    }

If there's an error, it is likely that the API has also returned a simple JSON object with a key 'error' and a value containing an error message. The code above checks if that is the case and if so, just sends the error message to the user. If not, then it uses the response header's status message and sends that to the user. If you want to check this error handling is working, just change accesstoken.txt to an invalid token and you should get an "Unauthorized" response.

When there's no error though, we can render the results. The documentation for the "List accounts" endpoint shows this example set of results:

[
  {
    id: "1234567890"
    name: "Your Account"
    slug: "your-account"
    active: true
    created_at: "2010-09-23T15:58:42+00:00"
    owner_id: "12345"
    owner: "Your User's Name"
  }
]

And it's this which we want to turn into a web page. This is where we use Handlebars' minimal templating engine to make life simpler:

    res.render("accounts", { "accounts": accounts });

We ask Express to render our new page as the response, telling it to use the "accounts" template. All pages get wrapped in HTML from views/layouts/main.handlebars because we set that as default layout when we configured Express. The "accounts" parameter directs it to use views/accounts.handlebars as the template to create the body of our page. That parameter is then followed by a JavaScript hash of any variables and names we'd like to pass to it – in this case we pass the parsed accounts object to the template engine. The template itself looks like this:

<h1>Accounts available</h1>  
<div class="accounts">  
<ul>  
{{#each accounts}}
<li><a href="/deployments/{{slug}}">{{slug}}</a> {{id}}/{{name}}</li>  
{{/each}}
</div>  

If you are new to Handlebars, the file is mostly standard HTML but the {{ and }} braces call out to Handlebars functionality. The accounts object we passed in was an array and Handlebars has the {{#each array}} function which will step through each element of that array, putting each element in scope. If we look at the returned data, we want to get the "slug" for the account and display that alongside the id and name. Most of the time "Handlebars" surround variable names so that {{slug}}, {{name}} and {{id}} get expanded to the appropriate values - even when inside quotes like the href= parameter.

In the template we create clickable links for the browser user to navigate to composed of "/deployments" and the account slug for a particular account. At this new browser URL we'll want to use that account slug to find out what deployments are associated with the account:

app.get('/deployments/:account_slug', function(req, res) {  
  account_slug = req.param("account_slug");

Express lets us extract named parameters from the requested URL to form our request. With the account slug in hand, we can now query the Get all deployments endpoint:

client.get(util.format("%s/accounts/%s/deployments", baseURL, account_slug),  
    httpArgs,
    function(deployments, response) {
      res.render("deployments", {
"account_slug": account_slug,
"deployments": deployments
      });
    })
});

This time we pass two parameters into the util.format call, the base URL and the account slug. This creates a URL something like https://beta-api.mongohq.com/accounts/accountslug/deployments. We'll skip doing a response check here for brevity and assume the call works. This returns a JSON object we'll call deployments. We pass that and the account slug to the renderer which will use a template like this:

<h1>Account: {{account_slug}}</h1>  
<a href="/">Back to accounts</a>  
<div class="deployments">  
<ul>  
{{#each deployments}}
<li><h2>Deployment: {{id}}</h2>  
  <a href="/database/backups/{{../account_slug}}/{{id}}">Backups</a>
  <ul>
    {{#each databases}}
    <li>
      <h3>{{name}} ({{id}})</h3>
      <a href="/database/stats/{{../../account_slug}}/{{../id}}/{{name}}">Statistics</a>
    </li>
    {{/each}}
  </ul>
{{/each}}
</div>  

We have nested each loops, one stepping through the deployments and generating a link to display the backups associated with that deployment, and another loop with steps through each deployment's databases, generating a link to show that database's statistics. The former needs just the account slug and deployment id, the latter needs those plus the database name.

app.get('/database/backups/:account_slug/:deployment_id',  
  function(req, res) {
    account_slug = req.param("account_slug");
    deployment_id = req.param("deployment_id");
    client.get(util.format("%s/deployments/%s/%s/backups", baseURL,
account_slug, deployment_id), httpArgs,  
      function(backups, response) {
res.render("backups", {  
  "account_slug": account_slug,
  "deployment_id": deployment_id,
  "backups": backups
});
      });
  })

app.get('/database/stats/:account_slug/:deployment_id/:database_name',  
  function(req, res) {
    account_slug = req.param("account_slug");
    deployment_id = req.param("deployment_id");
    database_name = req.param("database_name");
    client.get(util.format("%s/deployments/%s/%s/mongodb/%s/stats", baseURL,
account_slug, deployment_id, database_name), httpArgs,  
      function(stats, response) {
res.render("stats", {  
  "account_slug": account_slug,
  "deployment_id": deployment_id,
  "database_name": database_name,
  "stats": stats
});
      });
  })

The handlers use the List all backups for a deployment and Get database statistics endpoints respectively and then render their reports using Handlebars templates (backups.handlebars and stats.handlebars). All that's really changed here from the previous handlers are the number of parameters being passed around between the requesting URL, the REST API call and the response handler's render call.

The last thing needed to complete our example dashboard/explorer program is to start the Express server listening:

server.listen(3000, function() {  
  console.log('Listening on port %d', server.address().port);
});

The full source code of this app, including all the templates, is available on Github. Once you have cloned or downloaded it, filled in your accesstoken.txt file, and run npm install, we'll be ready to start the app up with node app.js. Once it is running, fire up the browser and navigate to localhost:3000 and browse the account, deployments, backups and statistics.

Now we've shown you how to use Go or Node.js to access the API, in the third part of this series, we'll tour the API's endpoints to give you a better understanding of how extensive your control can be over your MongoHQ databases.