Skip to content

Instantly share code, notes, and snippets.

@cdcarter
Created December 31, 2010 04:00
Show Gist options
  • Save cdcarter/760703 to your computer and use it in GitHub Desktop.
Save cdcarter/760703 to your computer and use it in GitHub Desktop.
couchapp_blog

CouchDb, CouchApps, Restful Asynchronous JavaScript, node.js, oh my!

This gist serves to be a tutorial or maybe a blog about building an application using all these mumbo jumbo new technologies that everyone around the world seems to love. We're going to start by implementing an application using only server-side javascript on a CouchDB server with couchapp, grow up to some client-side JS, and then extend outward to node.js to deal with some things server-side JS on Couch can't handle. We'll show off couch views, more advanced server-side JS, authentication, replication, Couch via jQuery, redirections, and more.

The Application

One thing I've learned from the process of building this app is that you need to define your problem before you start going around trying to solve it, so we'll start the series with a quick definition of what we need to accomplish. We're building an application for ticket reservations for a small theater company. The theater gives out it's tickets for free, and as a result of that, tends to sell out. The theater company tends to give tickets out in a first-come first-serve manner, but if they see someone very important needs a ticket and someone less important needs one, and there's only one left, well, they make the obvious decision.

What we need

So, we need an interface for:

  • requesting a ticket for a specific show
  • seeing all the ticket requests and how many have been confirmed
  • marking a request as confirmed
  • informing the requestor that their ticket is confirmed

CouchDB and Data

CouchDB, as you may know, is a document based database. If you already know that and already have fooled around with Couch, you may want to skip down to the section labelled "The Structure". Simply, this means that instead of tables of rows with columns and indexes and keys and all that, you have documents in a database. These documents can have any number of properties, differing from document to document, with any sort of data. You can find more at the CouchDB website. The best way to understand this is to just start fooling around. You can find easy installers for Couch at the CouchOne website, and you can also sign up for hosting there.

Your First Database

Once you've started your CouchDB instance, you can access the administration interface, Futon. If you are running Couch on your own computer, you can find it at http://localhost:5984/_utils. There will already be one database, _users, that will be useful to us later. For now, just create a new database, maybe called tickets, and click into it. Try creating, editing, and deleting some documents. As stated earlier, you can make the structure of any document almost anything you can imagine. If you click the "source" tab for a document, you'll see a JSON representation of it. JSON is the Javascript Object Notation, and it's how Couch stores the data you send to it. This is incredibly handy, because it means when we start interacting with our documents in server-side javascript, the data is already formatted for the language, no conversion or ORM necessary. This also means we can nest objects like lists or dicts within a document.

The Structure

So, let's start to lay out our data. We have shows, which have their individual performances, and we have ticket requests. In a traditional database, we'd make 2-3 tables for this, but Couch doesn't have tables. Instead we just have databases and documents. So, as a convention (that is by no means prescribed by Couch) developers tend to have a type property on their documents.

{
  "type": "show",
  "_id": "seascape",
  "name": "Seascape",
  "director": "Artist McGee",
  "capacity": 60,
  "performances" : [
    {"slug": "opening", "date": "March 3, 3pm"},
    {"slug": "second", "date": "March 3, 8pm"},
    {"slug": "closing", "date": "March 4, 8pm"}
  ]
}

Here, we have a show document for a show with three performances, and a capacity of 60 people. You can see that it's just a normal JavaScript object, and the performances property is just an array filled with objects, one for each performance, One thing to note is that we set the _id property of the document. If you don't set one at creation, it will default to a server provided UUID. We set the _id to be a slug for the show itself, making it much easier to refer to (and this will lead to prettier URLs).

Requests look just as you'd expect:

{
  "type": "request",
  "confirmed": false,
  "quantity": 3,
  "show": "seascape",
  "performance": "second",
  "name": "Joe Patron",
  "email": "joe@artconsumer.org"
}

In the request, we don't specify an _id, the server will provide it. We reference the _id of the show the request is for in the show property, and we reference the slug of the performance it is for in the performance property.

So, that's the data!

Hark! A CouchApp!

So now that we have an idea of our data, it's time to create our first CouchApp. A CouchApp is an application that lives entirely on the CouchDB. Couch serves up the data, the HTML, and handles all the processing (except for whatever you leave for the client to do). A CouchApp can benefit from all the scalability, simplicity, and awesome replication that comes to any Couch database. You can read more about why CouchApps are so awesome in this article: What the HTTP is a CouchApp?.

One thing I didn't mention earlier is that Couch's public API is just HTTP. You make GET requests at a document ID to retrieve. You PUT at it to update, and you POST documents to the database to create new ones. Futon, the administration interface is essentially a pretty UI on top of some AJAX requests. Anything you can do in Futon, you can do from curl. Futon is essentially a CouchApp, just built in.

CouchDB has an amazing book that explains a lot of this much better and in more detail than I will, so I recommend reading it. But, to be brief, a CouchApp is actually just a special type of document in the database. These are called design documents and they have _ids that start with _design/. It will become more and more obvious over time, but the _ character is used by Couch to show that something is more than just data. Design documents (or ddocs) are the only types that can have _ids that start with an _.

In the ddoc you can keep attachments for Couch to serve up (like stylesheets, images, and even HTML pages), your server-side javascript, and of course, any other data you may want to attach. A design document is just a normal document with a fancy name, and a few properties that it expects to be filled with javascript (if they exist).

Our CouchApp

So now that we have an idea of what design documents are and what CouchApps are, let's create one for our database. To do this, we'll use the couchapp tool. That's right, to make a CouchApp, we just use couchapp. Installing couchapp is pretty simple, so go take care of that and then let's start this.

From a command line, run:

$ couchapp generate ticketrequests

2010-12-30 22:56:12 [INFO] /Users/cdylancarter/ticketrequests generated.

This generates the shell of a CouchApp, though it does already have a few things in it we don't want. The skeleton generated has a cute little demo built in if your app is set up for it, but ours isn't so, let's delete the evently/, views/recent-items/, and vendor/couchapp/evently folders. This leaves us a directory structure that looks like

├── README.md
├── _attachments
│   ├── index.html
│   └── style
│       └── main.css
├── _id
├── couchapp.json
├── language
├── lists
├── shows
├── updates
├── vendor
│   └── couchapp
│       ├── _attachments
│       │   ├── A LOT OF JQUERY LIBRARIES
│       ├── lib
│       │   ├── A LOT OF LIBRARIES
│       └── metadata.json
└── views

This directory structure maps 1:1 with what the serialized document will look like when couchapp saves it to our database. Now let's create a .couchapprc file to make deployment quicker. It can look something like this:

{
	"env": {
		"default": {
			"db": "http://localhost:5984/tickets"
		},
		"production": {
			"db": "http://cdcarter.couchone.com/tickets"
		}
	}
}

This sets it up so the default couchapp push command will push our app to the tickets db on localhost. If we were to run couchapp push production however, it would go to my account on CouchOne's server. If you've set up users on your database already, you need to do username:password@localhost:5984, to ensure that you'll be able to push the application. This helpfully allows you to not have to type out your password every time you want to push!

So, what have we made...?

We'll push this to the server and see what we have in just a minute, but one last thing before we do that. The index.html file in _attachments/ is still expecting the skeleton's evently demo. Since we aren't using that, let's change it to something simpler. Open up index.html and delete the second <script> chunk, the one with all the javascript inside it. Now let's push this app!

$ couchapp push
2010-12-30 23:08:45 [INFO] Visit your CouchApp here:
http://localhost:5984/tickets/_design/ticketrequests/index.html

It worked! Let's take the response's advice and go to that page. It's our HTML file! Even though we haven't put in any content or any actual documents or written any logic, the server is definitely working and its even serving up our attachments.

As you may have noticed, the index.html file was in _attachments, but we didn't need to put that into the URL. Another example of how the _ is significant to Couch!

Now that we've got a CouchApp up and running, we'll need some data...

Let's add some data!

Before we do anything with the user interface, we'll need some seed data. We didn't say that we needed to create an interface for creating shows, because that's something the database admin (you) can do pretty easily. At least, for now. couchapp gives us a great way of creating and managing seed data for our database, too! Create a folder called _docs in your couchapp folder. We can put JSON files in there and couchapp will take care of it for us. I've created a_request.json, fleescape.json and bluewindow.json that look like this:

a_request.json

{
  "type": "request",
  "confirmed": false,
  "quantity": 3,
  "show": "seascape",
  "performance": "second",
  "name": "Joe Patron",
  "email": "joe@artconsumer.org"
}

fleescape.json

{
  "type": "show",
  "_id": "seascape",
  "name": "Seascape",
  "director": "Artist McGee and Pals",
  "capacity": 60,
  "performances" : [
    {"slug": "opening", "date": "March 3, 3pm"},
    {"slug": "second", "date": "March 3, 8pm"},
    {"slug": "closing", "date": "March 4, 8pm"}
  ]
}

bluewindow.json

{
  "type": "show",
  "_id": "bluewindow",
  "name": "Blue Window",
  "director": "Acting Director",
  "capacity": 75,
  "performances" : [
    {"slug": "opening", "date": "March 12, 3pm"},
    {"slug": "second", "date": "March 12, 8pm"},
    {"slug": "closing", "date": "March 13, 8pm"}
  ]
}

couchapp push and check out the database in Futon! Our documents are there, just as we hoped! It's worth noting that the request got an _id of a_request from the file name, since we didn't specify one in the document, but fleescape isn't the ID for the Seascape record. If you change the files and push again, the updates will show up in the database. If you edit the records in Futon however, and then push again, your edits will be reverted. Don't use _docs for documents that you might need to edit programatically.

Now that we have data, we can finally start writing our CouchApp!

The View

The CouchDB logo looks an awful lot like a visitor on the TV show the View, right? But really, the next step in our little CouchApp is to write a view. Views are Couch's way of filtering/extracting/summarizing and generally dealing with data. Again, the Couch book has an amazing chapter on Couch Views, so I defer to it for a deep study of how views work. It discusses a lot about views, view collation, dealing with keys, using views as JOINs and more. We'll talk about that sort of stuff later in the series, but for now we just need a very simple view.

The problem

People need to reserve tickets for a specific performance of a show, so we want to provide a welcome screen asking them to pick a show they'd like to reserve for, and once they've clicked on a show, we'll display a form where they can select a performance and reserve their tickets. The normal public API for Couch let's you GET a specific document ID, or you can get /_all_docs to get every document. We could get all the documents in the database and then iterate over them in our view code and only show the ones that are shows, but that seems pretty inefficient. Instead, we'll write a view to filter our data. We only want documents with a type of show. This is a pretty quick and simple view, so we're lucky it's our starting place.

Futon has a neat little function that let's you write a temporary view, so fire up Futon, open up the tickets database, and find the drop-down labelled "View". Select "temporary view", and get ready to start playing around. Futon presents us with the simplest view possible:

function(doc) {
  emit(null, doc); 
}

Hit "Run", and you'll see this just returns every document in our database, with the document in the Value column. What's that all about? And what is this function really doing?

CouchDB passes each document in the database to that map function. For every document that comes in, the map function looks at it and either emits something, or emits nothing. In this case, it emits every document. The emit() function takes two arguments, the key, and the value. Our view will only be a bit more complex. We'll continue to ignore that reduce section, because that's beyond what we need to do here. We just want to get every document where the type is show. Let's try implementing this in a temp view first...

function(doc) {
  if (doc.type == "show") {
    emit(doc._id,doc);
  }
}

So, if the document has the type show, we emit the document ID as the key, and the document itself as the value. Hit "Run" again and... yep! Only the shows are returned and a_request doesn't show up at all! Fantastic, it does what we want, but now we should make it a real view. Futon warns us right there, "temporary views are not suitable for use in production, as they are really slow for any database with more than a few dozen documents". couchapp makes this easy too.

Back to our shell:

$ couchapp generate view all-shows
$ tree views/
views/
└── all-shows
    ├── map.js
    └── reduce.js

Looks pretty straight forward. Again this view doesn't need a reduce function so lets rm views/all-shows/reduce.js and open up map.js in our favorite editor. The generator helpfully gave us the skeleton of a map function! But we already have ours, so let's paste it in, save, and couchapp push.

Now let's query the view! Views are accessed in a special way, and if something is special, that means it will have an _...

$ curl http://localhost:5984/tickets/_design/ticketrequests/_view/all-shows
{"total_rows":2,"offset":0,"rows":[
{"id":"bluewindow","key":"bluewindow","value":{"_id":"bluewindow","_rev":"12-b5ff3d5a9682cd396380a87fd028b931","director":"Acting Director","capacity":75,"name":"Blue Window","performances":[{"date":"March 12, 3pm","slug":"opening"},{"date":"March 12, 8pm","slug":"second"},{"date":"March 13, 8pm","slug":"closing"}],"type":"show","couchapp":{}}},
{"id":"seascape","key":"seascape","value":{"_id":"seascape","_rev":"18-0ea40e75cd05ec7d1e8cea3963824b64","director":"Artist McGee and Pals","capacity":60,"name":"Seascape","performances":[{"date":"March 3, 3pm","slug":"opening"},{"date":"March 3, 8pm","slug":"second"},{"date":"March 4, 8pm","slug":"closing"}],"type":"show","couchapp":{},"_deleted_conflicts":["3-f2b03abd55e776b48d9fd2b445168892"]}}
]}

As you can see, we request _view/view-name off of our ddoc, and there's the view response! It gives a bit of metadata and then the rows. There are two of them, and both shows, perfect!

The List

So we've started our foray into server-side Javascript, but we can't very well show that view output to our potential patrons and expect them to be able to figure out how to request a ticket from that, can we? Here come List functions. List functions are the CouchApp way of transforming the results of a view into something that we can actually use and present to our users.

Couch itself however doesn't really give us much in the way of making list functions easy, but our couchapp skeleton does. Couch expects a function that it can pass the request data and the results of the view, and it wants that function to return the response. All your HTML generation, data manipulation, everything you might do has to happen in that function. That just sounds ugly... But Couch has given us the ability to use CommonJS modules, and couchapp has included some very handy ones, specifically, the mustache templating library. The JavaScript implementation is fully functional and super easy to use with CouchDB. It was written by people writing CouchApps! You can find full explanations of mustache at the links above, but here's a quick primer.

You pass Mustache.to_html your template and a JavaScript object with keys you'll refer to in your view. Your template uses {{tags}} to show where you want replacements. You use {{#tags}}{{/tags}} for conditionals, loops, and changing the scope. That's all you get. Logic has to stay in your list function, and presentation stays in your templates. Here's a quick example:

The template

<h1>{{header}}</h1>
<ul>
  {{#shows}}
    <li>{{name}}</li>
  {{/shows}}
</ul>

The data object

{
  "header": "Select a Show",
  "shows": [
    {"name": "Seascape"},
    {"name": "Blue Window"}
  ]
}

and the output...?

<h1>Select a Show</h1>
<ul>
  <li>Seascape</li>
  <li>Blue Window</li>
</ul>

You can try it out further at the online demo.

So let's put together a list function and try this out.

$ couchapp generate list choose-show
$ tree lists/
lists/
└── choose-show.js

Pretty basic, and we now have the skeleton for our list function!

function(head, req) {

}

But wait, I said that Couch expects a function that it can pass the request and the view results, but this function seems to want something else... Here comes the part of the section where I point us once again to the CouchDB book, this time the chapter on List Functions. As it says head gives us the information about the view we're transforming, and req gives us the request object. So where's the data? The view results? The stuff we actually want?!

Couch gives us the tools to write very powerful views that query very large datasets. When you start having hundreds and hundreds of view results, you might want to stream your list function so the browser doesn't time out when you're collecting every result in memory. So Couch has a function called getRow() that we can iterate over and stream out our results. However, we're only going to have a few results, so we'll show that on another larger view. Instead, we'll just collect the results in an array and send that to our template. So, some code!

the list function

function(head, req) {
	var ddoc = this;																					 // get a reference to the design document
  var Mustache = require("vendor/couchapp/lib/mustache");    // imports the CommonJS module Mustache 
	var path = require("vendor/couchapp/lib/path").init(req);  // and the  module path, which gives us helpers
	var data = {"shows": []};                                  // creates a data object for our template
	
	var row;
	
	// we have to send headers before calling getRow(), it's just a couchdb quirk...
	start(resp = {
	 "headers": {
	   "Content-Type": "text/html"
	 }});
	
	// so let's iterate over our rows!
	while(row = getRow()) {
		// get the view value, which as you may remember is the full document
		doc = row.value;
		// set a link to our next piece in the puzzle, a show function to reserve for that show.
		doc.link = path.show("reserve",doc._id)
		// and put the doc into the template
		data.shows.push(doc);
	}
	
	// render the Mustache template!  you'll notice our templates are stored in the ddoc
	// JSON object, as strings.  couchapp handles uploading them as such for us.s
	send(Mustache.to_html(ddoc.templates.chooseShow,data))
}

and our template in templates/chooseShow.html

<html>
  <head><title>Reserve your tickets...today!</title></head>
  
  <body>
    <h1>Choose a show to reserve tickets for</h1>
    
    <ul>
      {{#shows}}
        <li><a href={{link}}>{{name}}</a></li>
      {{/shows}}
    </ul>
  </body>
</html>

couchapp push and fire up your browser to http://localhost:5984/tickets/_design/ticketrequests/_list/choose-show/all-shows. That is /database/_design/ddoc/_list/listName/viewName. And...look, it works! We have links for each one of our shows that goes to a hypothetical show function. I guess we know what we need to write next...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment