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
  • Have a happy & safe holiday weekend!
    August 29, 2014 at 3:55 PM
  • Useful #JAXB primer that illustrates the power from tools & frameworks that unobtrusively interact with POJOs - http://t.co/J1s5DpcsCp
    August 29, 2014 at 3:19 PM
  • The Keyhole team fantasy football league begins! Huge thanks to our commissioner @zachagardner. Good luck to all in the virtual gridiron!
    August 28, 2014 at 5:30 PM
  • Shout out to last year's winner of our Keyhole #fantasyfootball league - Adi Rosenblum (@adidas28). Will his reign continue? :-)
    August 28, 2014 at 5:30 PM
  • @dashaun That is the perfect way to put it - we are very excited! Great to meet you officially.
    August 28, 2014 at 4:53 PM
  • Check out a quick intro to Functional Reactive Programing and #JavaScript - http://t.co/4LSt6aPJvG #FRP http://t.co/m6G1Kqbwyi
    August 28, 2014 at 4:06 PM
  • When you pair #JAXB & #JPA, you can expect some "gotchas." Here are techniques to help you overcome the hurdles - http://t.co/J1s5DpcsCp
    August 27, 2014 at 1:56 PM
  • Interesting perspective - Famo.us talks big, but jQuery Foundation isn't worried: http://t.co/o9lLpPoh2G Thoughts?
    August 27, 2014 at 12:41 PM
  • We are delighted to say that RJ (@RJvXP) & Donna (@dkbdevlab) join Keyhole today. Welcome to the team!
    August 27, 2014 at 9:22 AM
  • RT @codeproject: Learning MVC - Part 5 Repository Pattern in MVC3 Application with Entity Framework by Akhil Mittal http://t.co/z603gpAH…
    August 27, 2014 at 9:15 AM
  • Know a bright new grad looking to learn? We're seeking a team member on our business side of the Keyhole house - http://t.co/GDvFVmoMF9
    August 26, 2014 at 3:29 PM
  • When you pair #JAXB & #JPA, you can expect to encounter some "gotchas." Techniques & learning to overcome hurdles - http://t.co/J1s5DpcsCp
    August 26, 2014 at 11:12 AM
  • Don't miss Mark Adelsberger's newest post on the Keyhole blog: #JAXB – A Newcomer’s Perspective, Part 2 http://t.co/J1s5DpcsCp
    August 25, 2014 at 1:21 PM
  • A huge welcome to Mike Schlatter who joins the KHS team today!
    August 25, 2014 at 12:33 PM
  • Never used JAXB? Check out a simple usage pattern that pairs #JAXB’s data binding capabilities with JPA - http://t.co/Ki9G04pLR6
    August 22, 2014 at 8:35 AM
  • Check out a quick intro to Functional Reactive Programing and #JavaScript - http://t.co/YGSsz5eynl #FRP http://t.co/m6G1Kqbwyi
    August 21, 2014 at 11:32 AM
  • Our team is riding #BikeMS to support the fight against Multiple Sclerosis. Get involved - http://t.co/GGObSd073P http://t.co/vZpWRXkQ3z
    August 21, 2014 at 9:09 AM
  • @dtkachev Thanks for the tweet! The server is back up now - sorry for the inconvenience. Have a good day!
    August 21, 2014 at 9:03 AM
  • RT @m_evans10: @joewalnes A SQL query goes into a bar, walks up to two tables and asks, "Can I join you?"
    August 20, 2014 at 3:55 PM
  • Have you read @wdpitt's newest book? - Web Essentials using #JavaScript & #HTML5. Free PDF download via @InfoQ: http://t.co/lesuPJ770I
    August 20, 2014 at 2:31 PM
Keyhole Software
8900 State Line Road, Suite 455
Leawood, KS 66206
ph: 877-521-7769
© 2014 Keyhole Software, LLC. All rights reserved.