A Mobile Application Served From CouchDB

by on November 27, 2012 12:00 am

“A DBMS as an application server? Is it Lotus Notes?”

CouchDB

CouchDB seemed odd to me in 2009 because it had … no SQL! This led me to play with it and then other NoSQL offerings. CouchDB features include:

  • Each database is a collection of key-value entities
  • Any database may be replicated to any other, at any time, or continuously
  • Each value is a JSON document
  • Sandboxed map/reduce, list, and show functions transform data
  • CouchDB serves HTTP requests for an attachment as an HTTP response

Those last two bullets caught my geeky eye. CouchDB could host all three MVC layers?

It includes Futon, a web-based administration console written in the public API. There is separation of MVC concerns imposed by a framework of data map/reduce view queries and UI show and list functions. A developer codes them, but they are stateless, sandboxed JavaScript functions having no side effects. An MVC controller is any programmer-supplied script that reacts to events by calling list or show functions through a REST API. I conceived a query-only mobile web application – a Couch App – that scales by replication.

I used Futon to learn to create a database and add a design document for holding an index.html, map/reduce, list, and show functions. It was easy to insert a couple of test documents, some functions, and then run those functions. I found it off-putting to have to write a map function to create a data-view to carry out a query that I could do in seconds in Oracle, MySQL, or MongoDB. I got past my objection. A CouchDB view became like a SQL view to me.

Opto3 Database

I hoped to use the simplest tools, trying to not depend on much beside what CouchDB offers. That dog didn’t hunt.

I created and edited view, show, and list functions using an opaque database as my workspace and Futon as my editor. All JavaScript functions must fit into JSON records. Escaping quotes and managing JavaScript braces embedded in JSON is intolerable. I wanted a local workspace that I could push to CouchDB after each code change – something that I could distribute in GitHub. Moreover, I needed CommonJS modules so I could require packages such as handlebars. How to insert shallow directory trees into a design document?

I fell in love with Kanso. It describes dependencies in a kanso.json file. Java Maven users will see a resemblance to a POM. Want a dependency? Name it in kanso.json. Kanso properly pushed artifacts described by that file.

Demo data? I downloaded a Maxmind free demo CSV file of 200 optometrist locations. The app would be an optometrist look-up. Kanso commands enable you to decorate a CSV file with CouchDB unique ID columns and convert to JSON. I let CSV column names become keys for CSV column values. I created an opto3 database through Futon. In a command window, I issued a Kanso command to upload demo JSON into my opto3 database.

Immediately, the _all_docs REST query was able to list my entire database. See Figure 1. Every document has a CouchDB-generated identity, plus a revision ID prefixed by a sequential revision number. The key can be any JSON object, duplicates allowed. CouchDB stores the ID value into the key if you don’t supply an explicit key.

During coding, I repeatedly used Kanso to push updates to my design document. Artifacts landed at intended places. A local directory was source for pushes.

Figure One

Figure 1

If I displayed the database in Futon, I could click a row to see the document data, as shown in Figure 2:

Figure 2: Document detail from clicking an ID

Figure 2: Document detail from clicking an ID

The REST path suffixed by an ID would return that ID’s document JSON in a browser or curl command line. The raw document for one optometrist is:

curl -X GET http://localhost:5984/opto3/3770717789a226c91f8ce4808e31cddd {“_id”:”3770717789a226c91f8ce4808e31cddd”,”_rev”:”1-c9818488639a0326aefc0624aba5bde2″,”npi”:”1003868795″,”full_name”:”David L Kjelland”,”first_name”:”David”,”middle_name”:”L”,”last_name”:”Kjelland”,”title”:”OD”,”mailing_address1″:”Po Box 211″,”mailing_city”:”Mineral Point”,”mailing_state”:”WI”,”mailing_zip”:”53565-0211″,”mailing_phone”:”608-987-3301″,”mailing_fax”:”608-987-3045″,”street_address1″:”318 High St”,”street_city”:”Mineral Point”,”street_state”:”WI”,”street_zip”:”53565-1219″,”street_county”:”Iowa”,”street_msa”:”0″,”street_phone”:”608-987-3301″,”street_fax”:”608-987-3045″,”gender”:”M”,”specialty_code”:”152W00000X”,”specialty”:”Optometrist”,”license_number”:”1588-035″,”license_state”:”WI”}

A Mobile Application

I created a single-page jQuery Mobile index.html consisting of four progressive drill-down logical pages:

  1. splash
  2. list of states showing counts of optometrists in each
  3. list of optometrist city addresses for a give state
  4. details of one optometrist

A database has one or more design documents denoted by a record ID of _design/something. I chose _design/opto. I inserted the map functions (model) needed to feed two list functions (view). In a Futon pull-down I could choose a view, see its code, and toggle any optional reduce function.

Figure 3 shows the view map code along with results for a count_by_state view. CouchDB calls the map function once per document (row). That function calls CouchDB’s emit function with a key and value, feeding into a new internal B-Tree collection. That expense occurs only when a view is created or updated. See “View Code” in the listing. Notice that CouchDB sorted documents by key. Of what use is a collection of “1s” keyed by sorted state abbreviations? Hang on.

Figure 3: Count-by-state map without reduce Figure 3: Count-by-state map without reduce

In Figure 4, I have set the “Reduce” option. The display morphed to a sorted list of state keys, each having a value that is a count of optometrists in that state. The reduce function produced the sum of values for each state key (i.e. the sum of each 1 value per instance in each state). Cool! I needed that data for a top-level list page that would display clickable USA states, each showing an optometrist count. I would pass the view reference to a list function that would generate a line of HTML for each row of the reduced collection. For example, there are two optometrist records for Arkansas in the demo database. There are two “AR” records in Figure 3. After the reduce, Figure 4 shows an “AR’ having a count of two. Good.

Figure 4: Count by state map with reduce

The drill-down panel for a given USA state needed a data view consisting of only a map function. See Figure 5. That function emits a compound key of state-city to a value of the optometrist address. If we filter that view for just one state in a consuming list function, the cities collate in natural ascending sort order, each having an optometrist address.

That’s perfect for populating the cities list after clicking a state. CouchDB always sets a record ID in every document. The detail panel page change only needs access to the ID to retrieve and display the details of that optometrist record.

Figure 5: Sorted list of state, cities with address values

I RESTfully tested the view, show, and list functions, trying both curl and a browser, before tackling the UI.

CouchDB View Functions

Views are data, not rendered HTML. I exported map/reduce functions to a CommonJS module.

// Used for panel two
exports.count_by_state = {
map: function(doc) { emit([doc.street_state], 1);  },
reduce: function (key, values, rereduce) { return sum(values);  }
};

// Used by panel three
exports.sorted_states_cities = {
map: function(doc) { emit([doc.street_state, doc.street_city], doc.street_address1);  }
};

CouchDB List functions

Two list functions return collections of HTML line items via the CouchDB provides() function and getRow() iterator. Handlebars produces the dynamic HTML.

// Populates list in panel two
exports.list_states =  function(doc, req) {

	provides('html', function() {
	        var Handlebars = require('handlebars');
	        var template = Handlebars.templates['stateName.html'];
	        var html = '';
	        while (row = getRow()) {
	            var context = { key: row.key, value: row.value};
	            html += template(context);
	        }
	        return html;
	    }
	)
};

// Populates list in panel three
exports.list_cities =  function(doc, req) {

	provides('html', function() {
	        var Handlebars = require('handlebars');
	        var template = Handlebars.templates['cityName.html'];
	        var html = '';
	        while (row = getRow()) {
	            var context = { id: row.id, state: row.key[0], city: row.key[1], address: row.value};
	            html += template(context);
	        }
	        return html;
	    }
	)
};
Couc

CouchDB Show function

A single show function uses a handlebars template to produce detail HTML from a document parameter and a document ID set by onclick.

exports.detail = function(doc, req) {

	var Handlebars = require('handlebars');
	var context = {
		_id: doc._id,
		full_name: doc.full_name,
		title: doc.title,
		street_address1: doc.street_address1,
		street_address2: doc.street_address2,
		street_city: doc.street_city,
		street_state: doc.street_state,
		street_zip: doc.street_zip,
		street_county: doc.street_county,
		street_phone: doc.street_phone,
		gender: doc.gender,
		specialty: doc.specialty
	};

    var template = Handlebars.templates['detail.html'];
	var html = template(context);
    return html;
};

Index.html

I used jQuery Mobile to create a four-page “single HTML page” mobile demo. You can replicate the application from http://mauget.cloudant.com/opto3 to your own CouchDB.

Controller

I tied views to list or show functions within scripts/controller.js through handlers fired by jquery-mobile-routerlite page change events. A jQuery-mobile button is a styled HTML hyperlink. Clicking a button causes a page-change event. The button on-click action stores an application-wide key used by the handler to find and render the result.

Handlebars Templates

Handlebars templates support the show function and two list functions. Recall that panel two and panel three onclick actions set a variable used by a template. See the italicization in the following two templates:

Panel two list item button template used by list_states

</pre>
<ul>
	<li><a onclick="APP.key='{{key}}';" href="#three"> {{key}} <span class="ui-li-count"> {{value}} </span></a></li>
</ul>
<pre>

Panel three list item button template used by list_cities

</pre>
<ul>
	<li><a onclick="APP.id='{{id}}';" href="#four"> {{address}}, {{city}}, {{state}} </a></li>
</ul>
<pre>

Panel four show template used by “detail”

</pre>
<dl title="Detail"><dt><strong>Name</strong></dt><dd>{{full_name}}, {{title}}</dd><dt><strong>Specialty</strong></dt><dd>{{specialty}}</dd><dt><strong>Gender</strong></dt><dd>{{gender}}</dd><dt><strong>Address</strong></dt><dd>{{street_address1}}</dd><dd>{{street_address2}}</dd><dt><strong>City, State</strong></dt><dd>{{street_city}}, {{street_state}}</dd><dt><strong>Zip</strong></dt><dd>{{street_zip}}</dd><dt><strong>County</strong></dt><dd>{{street_county}}</dd><dt><strong>Phone</strong></dt><dd>{{street_phone}}</dd></dl>
<pre>

Screen Captures from iPhone

I pushed the workspace to GitHub.

The following screens are in drill-down order taken from an iPhone 5:

Figure 6: Animated GIF splash screen

   -

Figure 7: List of US states with optometrist counts

-

Figure 8: Drill-down to Missouri optometrists

-

Figure 9: Details for one optometrist

The URL for a CouchApp is ugly. A production environment could redirect a “nice” URL to a Couch App that could reside in many CouchDB instances.

Conclusion

The geek in me says a mobile application hosted entirely in a DBMS that maintains good MVC separation is neat. New knowledge required, but not much coding involved. It could scale out by deploying Opto3 to many replicated instances. I have no idea how well it really scales as an application server. That and granular security are for future research.

CouchDB has structured update and delete functionality in addition to views. My app is a query-only app, but if the data changes, the app renders the change. I added a fake Alaska optometrist through a curl incantation. The app picked up the new state, city, and optometrist the next time I returned to the states page.

My paranoid side is wary. I had a learning curve, as would others. Would I suggest this all-in-one architecture to my client? Not now, for production. How about as an ad-tech proof-of-concept for their mobile applications? Certainly! I’d add CRUD and leverage role-based security also.

– Lou Mauget, asktheteam@keyholesoftware.com


References

  • Share:

6 Responses to “A Mobile Application Served From CouchDB”

  1. David says:

    Again, a concise fun to read blog…..I see this as a mechanism to provide public lists of mashable support/reference code type data for mobile and web applications. (i.e. lists of states, zips, countries, optometrists, etc… of course, jsonp might have to be used to get around origin issues.

  2. Why JSONP? The origin is alway within the server – CouchDB, not the browser.

    • David says:

      I’m thinking of using couchDB http/json to server up support code/reference objects for multiple browser applications, not the view/controller… I have a javascript/html timesheet application that needs a list of states, and and also a contact management appliction…. To get this reference data i call a couchDB/http/json endpoint. ?

  3. [...] has been made lately of “NoSQL” non-relational databases. A few weeks back, Lou wrote a post introducing CouchDB, a free Apache Foundation document datastore. Lou’s application was hosted entirely from CouchDB. [...]

  4. [...] lately of “NoSQL” non-relational databases. A few weeks back, Lou at Keyhole Software wrote a post introducing CouchDB, a free Apache Foundation document datastore. Lou’s application was hosted entirely from CouchDB. [...]

Leave a Reply

Things Twitter is Talking About
  • Interested in #Backbone & #Marionette but not sure where to start? Check out the Marionette-Require-Boilerplate: http://t.co/XDj43BwSS3 #SPA
    April 22, 2014 at 4:50 PM
  • Responsive Design can help in giving your users a consistent app experience across devices. Quick tutorial - http://t.co/BDrT5LvgRo
    April 22, 2014 at 2:31 PM
  • Tips & tricks to save time in the #Eclipse IDE - http://t.co/uGgCkchwNk (keystroke combos, navigation, time tracking & more)
    April 22, 2014 at 8:40 AM
  • Join us! Looking to add to our team a developer w/ advanced #JavaScript & #NodeJS exp (& love of tech variety). Info: http://t.co/cC9CU1RCF9
    April 21, 2014 at 7:35 PM
  • Looking into #ExtJS but don't know where to start? Check out our video tutorial series to find your way around - http://t.co/XFYDT6YNWA
    April 21, 2014 at 4:35 PM
  • We've been tinkering with JS library Famo.us since its public release 4/10. What we've learned so far via a POC app - http://t.co/S77TSKHDKd
    April 21, 2014 at 2:03 PM
  • RT @CompSciFact: Rivest, Shamir, and Adleman published the RSA public key cryptography algorithm in 1978.
    April 21, 2014 at 11:13 AM
  • DYK? When we share/RT/blog/etc, it doesn't mean that Keyhole endorses it - we just like variety of opinions! Info: http://t.co/MXhB9lE9tV
    April 19, 2014 at 3:01 PM
  • A huge welcome to Justin Graber who joined the Keyhole team this week!
    April 18, 2014 at 3:25 PM
  • Pssst... @kc_dc early bird pricing ends on Sunday. Shoot us a note if you want to save 10% off of your ticket with our sponsor promo code!
    April 18, 2014 at 2:49 PM
  • Join our team! Looking for a developer w/ advanced #JavaScript & #NodeJS experience (& love of tech variety). Info: http://t.co/cC9CU1RCF9
    April 18, 2014 at 11:21 AM
  • .@befamous has huge potential to make HTML5/JS/CSS web pages feel as native apps. Here's our inital tech takeaways - http://t.co/S77TSKHDKd
    April 18, 2014 at 9:50 AM
  • Why to use AngularUI Router instead of ngRoute - http://t.co/tBnj5ZCkOw
    April 17, 2014 at 7:55 PM
  • RT @joemccann: Total Number of GitHub Repositories by Programming Language http://t.co/30cekDsE4s
    April 17, 2014 at 4:25 PM
  • JSF + AngularJS = AngularFaces? http://t.co/mXvOTwVbb6 // Interesting insight. Thoughts?
    April 17, 2014 at 3:45 PM
  • RT @MikeGelphman: Great news, guys: @TobiasRush founder of @eyeverify is our latest @MobileMidwest speaker addition http://t.co/8fE8oNfPnX
    April 17, 2014 at 1:35 PM
  • .@befamous was released publicly 4/10 & we've been tinkering with it since. What we've learned so far via a POC app - http://t.co/S77TSKHDKd
    April 17, 2014 at 8:33 AM
  • Famo.us' main idea is for HTML5/JS/CSS web pages to feel like native mobile apps. So, @zachagardner tried it out - http://t.co/S77TSKHDKd
    April 15, 2014 at 6:40 PM
  • @JKFeldkamp Thanks for your RT! Such a neat technology. We're so excited @zachagardner is getting involved. Have a great day!
    April 15, 2014 at 4:00 PM
  • .@zachagardner has been tinkering with Famo.us (@befamous) released 4/10. What he's learned so far with a POC app - http://t.co/1jMqBfZURn
    April 15, 2014 at 2:29 PM
Keyhole Software
8900 State Line Road, Suite 455
Leawood, KS 66206
ph: 877-521-7769
© 2013 Keyhole Software, LLC. All rights reserved.