KansoJS

The surprisingly simple way to write CouchApps.

CouchApps are JavaScript and HTML based apps served directly from CouchDB. This means a super-simple stack and portable apps that can be shared and deployed using CouchDB's built-in replication. If you're not familiar with CouchApps, I highly recommend reading What in the HTTP is a CouchApp?.

The idea

Browser and server together

CouchDB already allows dynamic responses using list and show functions. Now, you can add client-side code too. That means you're able to fetch additional documents or perform other complex operations from the browser. CouchDB itself can then run the basic parts as a fall-back for search engines or browsers without JavaScript. The rewrites you define will fire in the browser too, allowing for automatic URL handling using pushState or location.hash.

function example_show(doc, req) {
    var content = templates.render('welcome.html', req, {});

    if (req.client) {
        // being run client-side, update the current page
        $('#main').html(content);
    }
    else {
        // fallback, returns a complete rendered page
        return templates.render('base.html', req, {main: content});
    }
};

Think about all the things I'm not having to write. I'm not having to set up Sammy.js on the client side with URL rewrite rules that match CouchDB. I'm not having to fetch the document for that URL before I'm able to template the page. I'm also not having to maintain two completely different sets of code, one for the browser and another for CouchDB.

CommonJS

The only problem with pushing all our application logic into the design doc is that its JSON, and JSON isn't a nice format to develop in. For starters, we have to write functions as strings and we can't include code from other files! This is why KansoJS has adopted the use of CommonJS modules instead of plain JSON for writing design docs.

Writing your JavaScript in CommonJS modules is much nicer than the traditional way of loading separate files which all modify the global scope. You may already know that you can use CommonJS modules in your CouchDB lists, shows, validate_doc_update and other functions. This makes it an ideal style to use throughout your application.

var templates = require('kanso/templates');

exports.rewrites = [
    {from: '/:id', to: '_shows/example/:id'}
];

exports.shows = {
    example: function (doc, req) {
        ...
    }
};

One problem with CouchApps is the reliance on structuring your data on the filesystem as you would like it to appear in the design doc. This means a complex hierarchy of folders and files to represent properties in a JSON document. It would be much better if you could represent this information in whichever way suits the project. By loading the design doc data from a module, its up to you how to structure it on the filesystem.

Instead of relying on CouchApp macros to include code from other files (by using special comments in your code), you can do it more cleanly using the CommonJS 'require' function.

Getting started

Installation

Install the most recent stable version of node, then clone Kanso from GitHub. Fetch the relevant submodules by doing this in the cloned directory:

git submodule init
git submodule update

You are then ready to install:

make && sudo make install

Starting a project

For this tutorial we'll be creating a basic app that displays albums by artist name. First, change to the location you wish to create the project directory in, then start a new project by doing:

kanso create recordstore

This will create a new directory called 'recordstore' containing a project skeleton to get you started. The contents of this directory should look something like the following:

recordstore
  |- lib                        commonjs modules relevant to the app
     |- app.js                  the module loaded as a design doc
  |- static                     static files to be attached to design doc
     |- jquery-1.4.2.min.js     some dependencies
     |- jquery.history.js
     |- json2.js
  |- templates                  templates used by the app
     |- base.html
     |- welcome.html
  |- kanso.json                 kanso settings
    

Before exploring these files in more detail, lets push the app to a couchdb database and check that everything works. For this tutorial, I'll assume you have a local couchdb database running at localhost:5984 (the default settings). In your project directory, do the following:

kanso push http://localhost:5984/recordstore

If you now visit http://localhost:5984/recordstore/_design/recordstore/_rewrite/ you should see a basic welcome page. Don't worry about the long and ugly URL for now, this can be fixed later using virtual hosts.

kanso.json

Let's take a look at the kanso.json file first. This file contains the settings for the app, and tells kanso which files and directories to load and how to load them.

{
    "name": "recordstore",        // the name of the design document
    "load": "lib/app",            // the module to get design doc properties from
    "modules": "lib",             // files and directories to load as commonjs modules
    "templates": "templates",     // templates directory
    "attachments": "static"       // files and directories to load as attachments
}

The name determines the id of the design doc, in this case it would be "_design/recordstore". Perhaps the most important part is the 'load' property. This tells kanso which module to require and use for the design doc. This means any exported values in this module will be used in the resulting design doc.

lib/app.js

This file contains some exported properties which are used in the resulting design doc, this is where the action happens and you get to define the behaviour of your app:

var templates = require('kanso/templates');


exports.rewrites = [
    {from: '/static/*', to: 'static/*'},
    {from: '/', to: '_show/welcome'}
];

exports.shows = {
    welcome: function (doc, req) {
        var content = templates.render('welcome.html', req, {});

        if (req.client) {
            $('#content').html(content);
            document.title = 'It worked!';
        }
        else {
            return templates.render('base.html', req, {
                title: 'It worked!',
                content: content
            });
        }
    }
};

You'll notice the skeleton project we generated comes with some rewrites already set up. The first exposes the static directory, the second rewrites the root url to a show function called 'welcome'. The welcome function then shows the welcome template (the page you've already seen after pushing the app). If this function is run client-side, it replaces the contents of the current page with the welcome message, otherwise it returns a new page.

There are a couple of interesting things here. Firstly, we've required a 'kanso/templates' module which doesn't exist in our project directory. This is one of the kanso modules automatically added to your app when you push to couchdb, and provides access to templates.

Secondly, we're actually using the context of the module inside the welcome function. With a normal CouchApp these functions are just converted to strings and inserted into the design doc, but kanso will try to proxy the function using a require call (remember I said CouchDB supports CommonJS?). This means we have access to the normal scope of the surrounding code, allowing us to use the kanso module we required at the top of the module inside the welcome function.

Note: The one important exception to this is view functions, CouchDB views do not support CommonJS modules in the same way and must be self contained.

templates/base.html

Kanso uses Dust templates. These templates look similar to Mustache, but provide some powerful additions such as filters, path lookups, blocks and better partials support. Dust templates can also be pre-compiled, meaning your templates are compiled when you 'kanso push', not each time a page is rendered.


<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <title>{title}</title> 
  </head> 
  <body> 
    <div id="content">
      {content|s}
    </div>
    <script src="{baseURL}/static/jquery-1.4.2.min.js"></script>
    <script src="{baseURL}/static/jquery.history.js"></script>
    <script src="{baseURL}/static/json2.js"></script>
    <script src="{baseURL}/kanso.js"></script>
  </body>
</html>

Learn more about the dust syntax

This template should be fairly self-explanatory. We describe a basic html document and render some variables: 'title', 'content' and 'baseURL'.

The 'content' variable passes through a filter 's'. This just means the value isn't escaped (we're going to be putting html in there!).

The variable 'baseURL' also deserves a mention. This is automatically made available in templates, and changes depending on whether the request uses a virtual host or not. If you've used CouchApps with virtual hosts before, you may be familiar with the problem of including stylesheets and javascript without knowing the current full path. Using baseURL fixes that, meaning you can run your CouchApp either behind a virtual host or access it directly in the /db/_design/doc/_rewrite/ style.

templates/welcome.html

<h1>It worked!</h1>
<p>Welcome to your new Kanso-powered app.<p>

Not much to see here, just some HTML we put into the 'content' section of the base.html template.

The Recordstore

OK, now we've explored a skeleton Kanso project, let's start building our own web app on top of it. Open Futon and view the recordstore database at http://localhost:5984/_utils/database.html?recordstore. You should have already created this when we pushed the new project to localhost in the 'Getting started' section above.

Add the following documents:

{
  "type": "album",
  "title": "Blue Lines",
  "artist": "Massive Attack",
  "cover": "http://userserve-ak.last.fm/serve/174s/47527219.png"
},
{
  "type": "album",
  "title": "Mezzanine",
  "artist": "Massive Attack",
  "cover": "http://userserve-ak.last.fm/serve/174s/38150483.png"
},
{
  "type": "album",
  "artist": "Underworld",
  "title": "Beaucoup Fish",
  "cover": "http://userserve-ak.last.fm/serve/174s/41665159.png"
}

This can be done with the fairly ugly curl command below:

curl -X POST -H "Content-Type: application/json" --data '{"docs": [{"type": "album", "title": "Blue Lines", "artist": "Massive Attack", "cover": "http://userserve-ak.last.fm/serve/174s/47527219.png"}, {"type": "album", "title": "Mezzanine", "artist": "Massive Attack", "cover": "http://userserve-ak.last.fm/serve/174s/38150483.png"}, {"type": "album", "artist": "Underworld", "title": "Beaucoup Fish", "cover": "http://userserve-ak.last.fm/serve/174s/41665159.png"}]}' http://127.0.0.1:5984/recordstore/_bulk_docs

Feel free to use your own selection of great albums if you wish, just make sure you add more than one by the same artist so we can demonstrate something later.

Creating views

To keep things clean and tidy, we're going to create a new module for storing views. Create a new file at lib/views.js with the following content:

exports.artist_names = {
    map: function (doc) {
        if (doc.type === 'album') {
            emit(doc.artist, null);
        }
    },
    reduce: function (keys, values, rereduce) {
        return true;
    }
};

Next, we need to export these views from the lib/app.js file so they are included in the design document. To include code from another file we can use require. Add the following to the end of lib/app.js.

exports.views = require('./views');

Now, if we push the app again we should see the new view available in futon. Push the app using the following command:

kanso push http://localhost:5984/recordstore

Then, visit http://localhost:5984/_utils/database.html?recordstore/_design/recordstore/_view/artist_names to see the results of the newly created view.

Templates and list functions

Now we have a view and some data, let's replace the welcome page with a list of artists. Once again, we'll create a new module for this code. Add a file at lib/lists.js with the following content:

var templates = require('kanso/templates');


exports.artists = function (head, req) {
    start({headers: {'Content-Type': 'text/html'}});
    var row, rows = [];
    while (row = getRow()) {
        rows.push(row);
    }
    // create a html list of artists
    var content = templates.render('artists.html', req, {rows: rows});

    if (req.client) {
        // if client-side, replace the HTML of the content div with the list
        $('#content').html(content);
        // update the page title
        document.title = 'Artists';
    }
    else {
        // if server-side, return a newly rendered page using the base template
        return templates.render('base.html', req, {
            title: 'Artists',
            content: content
        });
    }
};

This list function will be used with the artist_names view we created earlier. When run server-side it will render a complete HTML page, and when run client-side it will update the current page's content and title instead.

Again, we need to export this from lib/app.js too.

exports.lists = require('./lists');

You may have noticed we used a template 'artists.html' in our list function. This template doesn't exist, so we need to create it. Add a file at templates/artists.html with the following content:

<h1>Artists</h1>

<ul>
  {#rows}
    <li><a href="{baseURL}/{key|uc}">{key}</a></li>
  {/rows}
</ul>

Finally, let's replace the welcome page with our new list function. Update lib/app.js to the following:

exports.rewrites = [
    {from: '/static/*', to: 'static/*'},
    {from: '/', to: '_list/artists/artist_names', query: {group: true}}
];

exports.views = require('./views');
exports.lists = require('./lists');

We've removed the old welcome show function and added a rewrite from the root URL to the new artists list function. Push the app again, then visit http://localhost:5984/recordstore/_design/recordstore/_rewrite/. You should see a list of artist links (the linked pages don't exist yet).

To be continued...

This tutorial is a work in progress, if you have any additions then please fork the Kanso project on GitHub and edit the index.html file. Any questions or comments can be sent to caolan on GitHub.

To be kept informed, Watch the Kanso project on GitHub or follow @caolan on Twitter.

Fork me on GitHub