Showing Documents in Custom Formats

CouchDB’s show functions are a RESTful API inspired by a similar feature in Lotus Notes. In a nutshell, they allow you to serve documents to clients, in any format you choose.

A show function builds an HTTP response with any Content-Type, based on a stored JSON document. For Sofa, we’ll use them to show the blog post permalink pages. This will ensure that these pages are indexable by search engines, as well as make the pages more accessible. Sofa’s show function displays each blog post as an HTML page, with links to stylesheets and other assets, which are stored as attachments to Sofa’s design document.

Hey, this is great—we’ve rendered a blog post! See Figure 1, “A rendered post”.

Figure 1. A rendered post

The complete show function and template will render a static, cacheable resource that does not depend on details about the current user or anything else aside from the requested document and Content-Type. Generating HTML from a show function will not cause any side effects in the database, which has positive implications for building simple scalable applications.

Rendering Documents with Show Functions

Let’s look at the source code. The first thing we’ll see is the JavaScript function body, which is very simple—it simply runs a template function to generate the HTML page. Let’s break it down:

function(doc, req) {
  // !json templates.post
  // !json blog
  // !code vendor/couchapp/template.js
  // !code vendor/couchapp/path.js

We’re familiar with the !code and !json macros from Chapter 12, Storing Documents. In this case, we’re using them to import a template and some metadata about the blog (as JSON data), as well as to include link and template rendering functions as inline code.

Next, we render the template:

  return template(templates.post, {
    title : doc.title,
    blogName : blog.title,
    post : doc.html,
    date : doc.created_at,
    author : doc.author,

The blog post title, HTML body, author, and date are taken from the document, with the blog’s title included from its JSON value. The next three calls all use the path.js library to generate links based on the request path. This ensures that links within the application are correct.

    assets : assetPath(),
    editPostPath : showPath('edit', doc._id),
    index : listPath('index','recent-posts',{descending:true, limit:5})
  });
}

So we’ve seen that the function body itself just calculates some values (based on the document, the request, and some deployment specifics, like the name of the database) to send to the template for rendering. The real action is in the HTML template. Let’s take a look.

The Post Page Template

The template defines the output HTML, with the exception of a few tags that are replaced with dynamic content. In Sofa’s case, the dynamic tags look like <%= replace_me %>, which is a common templating tag delimiter.

The template engine used by Sofa is adapted from John Resig’s blog post, “JavaScript Micro-Templating”. It was chosen as the simplest one that worked in the server-side context without modification. Using a different template engine would be a simple exercise.

Let’s look at the template string. Remember that it is included in the JavaScript using the CouchApp !json macro, so that CouchApp can handle escaping it and including it to be used by the templating engine.

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %> : <%= blogName %></title>

This is the first time we’ve seen a template tag in action—the blog post title, as well as the name of the blog as defined in blog.json are both used to craft the HTML <title> tag.

    <link rel="stylesheet" href="../../screen.css" type="text/css">

Because show functions are served from within the design document path, we can link to attachments on the design document using relative URIs. Here we’re linking to screen.css, a file stored in the _attachments folder of the Sofa source directory.

  </head>
  <body>
    <div id="header">
      <a id="edit" href="<%= editPostPath %>">Edit this post</a>
      <h2><a href="<%= index %>"><%= blogName %></a></h2>

Again, we’re seeing template tags used to replace content. In this case, we link to the edit page for this post, as well as to the index page of the blog.

    </div>
    <div id="content">
      <h1><%= title %></h1>
      <div id="post">
        <span class="date"><%= date %></span>

The post title is used for the <h1> tag, and the date is rendered in a special tag with a class of date. See the section called “Dynamic Dates” for an explanation of why we output static dates in the HTML instead of rendering a user-friendly string like “3 days ago” to describe the date.

        <div class="body"><%= post %></div>
      </div>
    </div>
  </body>
</html>

In the close of the template, we render the post HTML (as converted from Markdown and saved from the author’s browser).

Dynamic Dates

When running CouchDB behind a caching proxy, this means each show function should have to be rendered only once per updated document. However, it also explains why the timestamp looks like 2008/12/25 23:27:17 +0000 instead of “9 days ago.”

It also means that for presentation items that depend on the current time, or the identity of the browsing user, we’ll need to use client-side JavaScript to make dynamic changes to the final HTML.

    $('.date').each(function() {
      $(this).text(app.prettyDate(this.innerHTML));
    });

We include this detail about the browser-side JavaScript implementation not to teach you about Ajax, but because it epitomizes the kind of thinking that makes sense when you are presenting documents to client applications. CouchDB should provide the most useful format for the document, as requested by the client. But when it comes time to integrate information from other queries or bring the display up-to-date with other web services, by asking the client’s application to do the lifting, you move computing cycles and memory costs from CouchDB to the client. Since there are typically many more clients than CouchDBs, pushing the load back to the clients means each CouchDB can serve more users.