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
  • In Part 2 of our series on creating your own #Java annotations, learn about processing them with the Reflection API - http://t.co/E1lr3RmjI7
    September 19, 2014 at 12:15 PM
  • The life of a Keyhole consultant - A Delicate Balance: It’s What We Do http://t.co/ToRpWY3aix Blog as true today as the day it was written.
    September 19, 2014 at 9:50 AM
  • 7 Things You Can Do to Become a Better Developer - http://t.co/llPNMUN8nQ
    September 19, 2014 at 8:43 AM
  • .@jessitron Good luck, you'll do great! Our team really enjoyed your KCDC14 talks.
    September 18, 2014 at 10:19 AM
  • RT @woodwardjd: 7 deadly sins of programming. I think I did all of this last week. #strangeloop http://t.co/f7QFq1SpqW
    September 18, 2014 at 10:03 AM
  • In Part 2 of our series on creating your own #Java annotations, learn about processing them with the Reflection API - http://t.co/E1lr3RmjI7
    September 17, 2014 at 3:18 PM
  • We send out our free monthly tech newsletter tomorrow - dev tips/articles via email. Not on the list? Sign up: http://t.co/h8kpjn419s
    September 16, 2014 at 2:58 PM
  • Want to chuckle? If programming languages were vehicles -http://t.co/quqHsUFCtR #funny
    September 16, 2014 at 11:41 AM
  • In Part 2 of our series on creating your own annotations, learn about processing #Java annotations using Reflection: http://t.co/DJZvQuarkc
    September 16, 2014 at 9:06 AM
  • Don't miss @jhackett01's newest post on the Keyhole blog - Processing #Java Annotations Using Reflection: http://t.co/E1lr3RmjI7
    September 15, 2014 at 12:02 PM
  • We're pretty excited - Keyhole's #BikeMS team raised 158% of their fundraising goal to benefit @MidAmericaMS. Plus, they had a great ride!
    September 15, 2014 at 10:38 AM
  • A huge welcome to David Kelly (@rheomatic) who officially joins the Keyhole team today! :-)
    September 15, 2014 at 10:00 AM
  • Sending warm thoughts to @eastlack, @cdesalvo, @wdpitt & all participating in #BikeMS this AM. Thanks for helping in the fight against MS!
    September 13, 2014 at 8:10 AM
  • .@rheomatic We are so excited to have you joining the team! Welcome :-)
    September 12, 2014 at 4:11 PM
  • As the official holiday is a Saturday, we're celebrating today! Happy (early) #ProgrammersDay to you! http://t.co/1CvUfrzytE
    September 12, 2014 at 1:55 PM
  • Tomorrow @cdesalvo, @eastlack, & @wdpitt are riding #BikeMS to benefit @MidAmericaMS. You can get involved, too - http://t.co/9boQwEUxth
    September 12, 2014 at 11:00 AM
  • RT @AgileDevs: 5 tips for great code reviews http://t.co/9PdbtEv0z8
    September 11, 2014 at 2:53 PM
  • The BEMs of Structuring #CSS - http://t.co/159suYtfx6 A quick introduction to the Block Element Modifier methodology.
    September 10, 2014 at 2:49 PM
  • A huge welcome to Joseph Post (@jsphpst) who has joined the Keyhole team this week!
    September 10, 2014 at 9:52 AM
  • @TheGrisExplores Absolutely, and thanks for the compliment! Here's an article that you might find helpful, too - http://t.co/7oxpaohCS1
    September 9, 2014 at 2:22 PM
Keyhole Software
8900 State Line Road, Suite 455
Leawood, KS 66206
ph: 877-521-7769
© 2014 Keyhole Software, LLC. All rights reserved.