Hello, this is Jan Lehnardt and you're visiting my blog. Thanks for stopping by.
plok — It reads like a blog, but it sounds harder!
↑ Archives
To illustrate how easy and straightforward writing applications for CouchDB is, we are going to build a simple todo-list application in Javascript. You should be familiar with HTML and Javascript and the DOM. You do not need any Ajax experience. Although we are going to use it, all will be abstracted away.
The interface is quite plain, as is the functionality. This is only to demonstrate how to work with CouchDB and not meant as a real application. We could turn this into something nice with some spit & polish.
We take a top level view here, working our way from the user’s perspective down to the actual code. This ensures we do not screw up the application for the user by mapping its inner workings to the user’s model.
When you open the app you are greeted with an empty input box and a list of todos (or no todos, in case you were working hard). You can type in a single-line todo item, hit enter and that line appears on top of the list. Finally, the input field gets reset and you can enter another item. To mark an item done, click the X next to it. It will disappear.
Boy this looks trivial, but it captures the essence of a todo list. There are plenty of opportunities how to improve things, but let’s nail the basics first.
Setting up the UI is quite simple. HTML boilerplate, a form with an input field and an empty list:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="application/xhtml+xml; charset=utf-8"/>
<title>My Todos</title>
</head>
<body id="index">
<form id="add" action="">
<input type="text" name="todo" value="" id="todo"/>
</form>
<ul id="todos"></ul>
</body>
</html>
Open this in your browser and it will look like the screenshot above sans todo items.
You might want to try things out as we go and follow along on your system. The easiest setup is highjacking CouchDB’s webserver to serve the application. This solves the immediate issue of browser security, where Ajax calls can only be made to the domain the code came from due to the same origin policy.
If you installed CouchDB with the instructions found in the README file, the built in webserver’s document root is /usr/local/share/couchdb/www/
. In there create a new directory and place all the files you are going to create in there. You can now access your todo app clone at http://localhost:5984/_utils/CouchDbToDo/
.
All the magic is happening here. We are not going to use one of the great Javascript frameworks to keep things generic. Moving things to jQuery took twenty minutes when I tried it and I assume with prototype, it would be similar.
What we are going to use is CouchDB’s built in Javascript library. It provides as an easy interface to CouchDB without having to worry about manually managing HTTP requests. It, in turn, is based on Douglas Crockford’s object.toJSON()
library which we will include as well. We put both files next to our HTML file and add these two line to the <head>
:
<script src="../script/couch.js" type="text/javascript" charset="utf-8"></script>
<script src="../script/json.js" type="text/javascript" charset="utf-8"></script>
Now we are all set for writing some code. We start off with a convenience function. A shortcut for the ever-so-annoying-to-type document.getElementById()
. Although Crockford advises against using $()
for the alias, for the sake of ubiquity, we swim with the fishes.
<script type="text/javascript" charset="utf-8">
function $(e)
{
return document.getElementById(e);
}
</script>
Before going into actual storage, we work on the dynamic list population. That is, making input from the text field available in the list below it. We need two things for that: An event handler that gets called when a user hits enter in the field and a list insertion routine that does the necessary DOM fiddling.
First the event handler. We are using the unobtrusive Javascript method here. It avoids polluting HTML markup with non-semantical onSomething
attributes that include actual Javascript. We want our HTML nice and clean and concerns separated.
To set up the event handler for the input field, we need another event handler to see when our HTML has been loaded and rendered, i.e. when it can be dabbled with. This is the window’s onload
handler. We set it up as follows.
window.onload = function()
{
//code
}
I am aware that this is not the ultimate solution, but it will do for the sake of the demo. Now, to setup the enter event handler.
window.onload = function()
{
// what to do when hitting enter in the input field
$("add").onsubmit = function()
{
var value = $("todo").value;
$("todo").value = ""; // reset input box
if(value) { // don't add empty todos
Couch_Todo.insert({text:value});
}
// don't submit the form
return false;
}
// focus the input field when the document loads
$("todo").focus();
}
Heck, what is going on here?! add
is the id
of our form and we define a function to be run in case of its regular submission from the browser. We first capture the value of the input field directly followed by resetting said input field. This makes for a great user experience, things appear to happen instantaneously. In case the value is not an empty string (the user could have hit enter on the empty input field), we call a magic Couch_Todo.insert()
function. Well, not so magic, we will explore in a just a second. For now, we just assume that this handles the list insertion. We finish the form submission event handler with a return false;
statement to make sure the form does not actually gets sent. We do not want that.
Bonus material: The $("todo").focus();
line does exactly what the comment says (yay!). Reducing UI interaction is a good idea, so we focus the input field automatically.
Now, what’s with Couch_Todo.insert()
? We are creating a custom object that contains all our data and functions. This is Good Practice again, as it does not pollute the global Javascript namespace. For a standalone application, this is not a huge concern, but once we start integrating other code libraries or get integrated by someone else, it is a good idea to keep all related stuff in a single place that does not clash with others. Hence, we create the global object Couch_Todo
as our main entry point for our functionality. Inside our inline <script>
-tag, we define it, like this:
var Couch_Todo =
{
insert: function(todo)
{
}
}
This a simple object that has a single method insert()
that can be called by writing Couch_Todo.insert()
and lo and behold, that is, what our event handler above does. Let’s move on.
What the insert()
method does is creating a new <li>
element, putting the input field’s value in and stacking it onto the list in our UI. We use bit of DOM scripting here, but really, just a bit.
var Couch_Todo =
{
insert: function(todo)
{
// DOM-fu
var list_item = document.createElement("li");
list_item.innerHTML = todo.text;
$("todos").insertBefore(list_item, $("todos").firstChild);
}
}
The fist line creates a new DOM object of the type HTML li Element
. The seconds appends our item text. Note that we use another Javascript object here. In fact, you could have noticed it earlier, when we called Couch_Todo.insert({text:value});
. So we have an object with a property text
that carries our input fields value. Easy.
Next we take the whole <li>
element and use a combination of insertBefore()
and firstChild
to put it on top of our list. This illustration might make it more clear what is going on. If not, and you understand everything and this confuses you, ignore it.
We can now add new todo items to our list. But once we close the browser or reload our HTML, all is gone. We now need to make our input persistent. We need to save the todo items and we are storing them in CouchDB.
There are two things we need in order to save data in CouchDB, first the CouchDB Javascript library that frees us from handling Ajax calls to CouchDB’s REST API ourselves, and second a new method add()
for our Couch_Todo
object.
We already included the CouchDB Javascript library earlier, so we just need to start using it: new CouchDB('todo')
. todo
is the name of the database we’re going to use. You can choose any name here as long as it complies with CouchDB’s naming conventions for naming databases. The snippet above creates a new CouchDB
object that we conveniently store in a property db
of our Couch_Todo
object
var Couch_Todo =
{
db: new CouchDB("todo"),
insert: function... // and so on
}
We then add a our add()
method that handles the actual saving of our todo item. It takes our todo object (the one that holds the input value) and first saves it into CouchDB, then calls insert()
to add it to the list. So we are changing the flow a bit. In our event handler for the input field, we are no longer calling insert()
. We now call add()
which adds our item and calls insert()
for us.
$("add").onsubmit = function()
{
var value = $("todo").value;
$("todo").value = ""; // reset input box
if(value) { // don't add empty todos
Couch_Todo.add({text:value});
}
// don't submit the form
return false;
}
Above is what our event handler looks like now. Note that we omitted a bit of the surrounding code for brevity. Below is the add()
method. It is pretty simple.
add: function(todo)
{
this.db.save(todo);
this.insert(todo);
}
The CouchDB
object has a method save()
that we just pass in our todo object. The method goes and tries to create a new document in our todo
database. A document is the central data structure in CouchDB. It is a lose collection of keys and values, just like our todo object. We assume success here and don’t care for potential errors. When data is saved into CouchDB, it gets assigned an id
an identifier by which we can refer to that same data again later. save()
puts this id
into our todo object so we can use it later. And we will, stay tuned.
You can now add todo items to the list and they get saved in CouchDB. To verify this, go to the CouchDB administration utility probably running at [http://localhost:5984/_utils/
and see for yourself the contents of the todo database.
But what happens when we reload our page? All the todo items are gone again. Slapping our forehead we realize that we do in fact save them, but we do not fetch them back when we are loading the page. Let us get to that.
We first need a view. A view is like filter for all documents in a CouchDB database. We only have todo documents, but we could have more. So we want to make sure we only get todo documents. We also use a view to specify the data we want to get out of the document, when we query the view.
Setting up a view is pretty straightforward. A view consists of a name (easy) and a Javascript function (easy as well). All view functions do have the same structure.
function(doc)
{
map(null, doc);
}
They receive a document object and call, for each object that should be in the view, a function called map()
; Above is the easiest view function available. It calls map()
for each document and just puts all documents into the view. You can, however, make some decisions based on the document, whether to include it or not.
function(doc)
{
if("todo" == doc.type) {
map(null, doc);
}
}
This only includes documents, that have a field type
with the value todo
. Note that type
is just an arbitrary name, you could easily ask for all documents with a field that has a certain length.
function(doc)
{
if(doc.text.length > 100) {
map(null, doc);
}
}
What is with the null
parameter? A view’s result is sorted by a key. By default, this key is the document’s id. And by calling map()
with null as the first parameter, we ask CouchDB to use the default key. We can submit our own keys as well, if we prefer.
function(doc)
{
if("todo" == doc.type) {
map(doc.date, doc);
}
}
Now we have a view that is sorted by the value of the date
field in each document of the type todo
. The view for our todo application looks similar to the first view I’ve shown above:
function(doc)
{
map(null, {text:doc.text});
}
Instead of doc
as the second argument, which implies we are only interested in a document’s id (and revision), we are tell the view to save a Javascript object that should look familiar. Correct, we used that same structure to move the value of our input field to the list insertion method.
We can write nice Javascript functions all day and CouchDB wouldn’t know about them. How to tell CouchDB to use our functions? How to create a view? We could go and solve that programatically, but we won’t. There’s a simpler way that uses less code. And less code is better in this case. Go to CouchDB’s administration utility (you might still have it open from verifying the todo items get saved). You see a list of databases, very likely only the todo
database. Click on it. You then see a list of documents in the database. To create a view, click on the Create Document… button. You will get asked to name the new document. Call it “_design/todo”. After clicking OK, it will appear in the list of documents. Yes, views are just a special kind of documents. Next we need to tell CouchDB that this view is defined using Javascript, as CouchDB does not actually care which language you write the view functions in. Create a new field in the document and name it language
. Its value shall be "text/javascript"
. Do not forget to include the surrounding inch quotes. Now add another field views
with the value {"all":"function(doc) { map(null, {text:doc.text});}"}
Finally, save the document.
That were quite a few words. Here’s a short movie demonstrating the whole procedure.
We now have a view that, when queried, gives us a list of Javascript objects that represent one of our todo items each. Cool.
Now we can populate our todo list with the items we put in. They will load automatically, when our HTML page gets loaded. To bulk work here is done by a new method of our Couch_Todo
object. The load
function queries our new view, reads the results and invokes our insert()
method to handle the DOM-magic we solved earlier.
CouchDB’s Javascript library makes it conveniently easy to query a view:
var all_docs = this.db.view("todo/all");
all_docs
is now an array with all our todo items, ready at our disposal. We loop over it and insert each into our HTML list.
load: function()
{
var all_docs = this.db.view("todo/all");
// in case there are no todos, don't go loopin'
if(all_docs && all_docs.total_rows > 0) {
for(var idx = 0; idx < all_docs.total_rows; idx++) {
var todo = all_docs.rows[idx];
this.insert({
text:todo.value.text
});
}
}
}
When we now load the todo application in our browser, we are going to see all the todos we put in earlier, new ones get saved and survive reloads. We are done.
Are we?
What we have is quite nice so far, but we can do better. We can’t delete todo items (once they are done e.g.). We might see, when we load saved todo items that they appear in random order. And when there are no todos, all we see is input field and that is not very instructive.
The last point is the easiest. We want to show a message when there are no todo items. That is, either the first time a user opened our app or when when he finished all his tasks (remember, we are going to add deletion, too).
First we prepare the necessary HTML code.
<div id="empty">Enter todo items in the box above and hit enter.</div>
We add this into our HTML, just before the closing </body>
tag. To handle its appearing or disappearing, we create a new method for Couch_Todo
that we call updateEmpty()
. It will check, if there are any todo items and show or hide our message accordingly. All we need to do is to call it, when we change the number of items.
updateEmpty: function()
{
if(0 == $("todos").childNodes.length) {
var display = "block";
} else {
var display = "none";
}
$("empty").style.display = display;
}
These are a lot of lines for a little task. Yet they are easy to understand. Here is a slightly harder to read, but shorter version.
updateEmpty: function()
{
$("empty").style.display = (0 == $("todos").childNodes.length)?"block":"none";
}
Now, where are the places we change the number of todo items? For now, only when inserting them, either by loading them from CouchDB or by entering new ones. The insert()
function now looks like this:
insert: function(todo)
{
// DOM-fu
var list_item = document.createElement("li");
list_item.innerHTML = todo.text;
$("todos").insertBefore(list_item, $("todos").firstChild);
this.updateEmpty();
}
Now, when first loading the application, we see our message. Once we add a new todo, the message disappears.
We are now going to organise your todos. The ones we put on top will stay on top, after we reloaded our page. To do this, we save a timestamp (the number of seconds since a long time ago) along with our todo items and ask our view to sort by it. Views can do that. So we can make sure the newest todo stay always on top.
First we expand our add()
method. When creating a new view, we capture the time (gotcha!) and store it with the todo.
add: function(todo)
{
todo.time = (new Date()).getTime();
this.db.save(todo);
this.insert(todo);
},
All we need to do now is to update our view function. To do that, go back into the administration utility and add doc.time
to the index of the view.
function(doc)
{
map(doc.time, {text:doc.text});
}
Now, the todo items we did put in so far will remain in their strange order but, all the new ones are going to be stacked on top of our list. To deal with our older items, we could go into the administration utility and add a timestamp manually.
The last one is a bit more complex, but not that hard in the end. To be able to delete todo items, we need a UI element that triggers a deletion. We also need an event handler that executes the deletion and we need to pull some strings together for convenience.
To keep it simple, we use a hyperlinked X
that we append to our todo items. The link carries an inline onClick
event handler that triggers our deletion. Yes, inline event handlers are generally a bad idea as noted above. In this situation it attributes for less code, so we do that. The event handler sends the <li>
element our todo item consists of to our deletion routine. We add yet another method to Couch_Todo
: remove()
. It takes the aforementioned <li>
element and removes it both from our CouchDB database and the todo list.
To make this actually work, we need to take a few extra steps. To be able to delete a document from CouchDB we need its id and its revision. You can store multiple versions of the same document in CouchDB and you refer to them with the respective revision. To be able to delete it, you need the latest revision. We somehow need to keep track of the revision. In addition to displaying our todo items in the HTML list, we keep a dictionary of them in our Couch_Todo
object so we can refer to them later. And to actually get back from an <li>
element (the one that our deletion event handler sends along) to the actual todo object, we store the accompanying document id as an attribute into the <li>
object.
Now slowly, one step at a time. We add an empty dictionary to the Couch_Todo
object where we can later put our todo objects.
var Couch_Todo =
{
_todos:{},
db: new CouchDB("todo"),
insert: function(todo)...
}
In our insert function, we add two lines. One to store the object in our _todos
array and the other to add the document id to the <li>
element.
insert: function(todo)
{
// DOM-fu
var list_item = document.createElement("li");
list_item.innerHTML = todo.text;
$("todos").insertBefore(list_item, $("todos").firstChild);
this.updateEmpty();
//new lines
this._todos[todo._id] = todo;
list_item.id = todo._id;
}
And finally, we add our UI target, the hyperlinked X
.
insert: function(todo)
{
// DOM-fu
var list_item = document.createElement("li");
list_item.innerHTML = todo.text + ' <a href="#" onClick="Couch_Todo.remove(this.parentNode); return false;">X</a>';
$("todos").insertBefore(list_item, $("todos").firstChild);
this.updateEmpty();
this._todos[todo._id] = todo;
list_item.id = todo._id;
}
Enough with the preparations! Granted, they are not that tricky, but damn, we want to delete stuff here, now.
remove:function(li)
{
var todo = this._todos[li.id];
this.db.deleteDoc(todo);
this._todos[li.id] = null;
$("todos").removeChild($(li.id));
this.updateEmpty();
// focus the input field
$("todo").focus();
}
This should be pretty self explaining (if you made it to this point). As a bonus, after having deleted an item, we focus the input field again. We can start adding new items immediately.
Now we are done for today. There is lots to do with this simple app. Visuals come to mind. Assignment of todos, distributed collaboration, search, reordering and all that. I may come back to that at a later time.
If you don’t want to assemble the pieces yourself, here’s all the code sans external scripts and the view we created in CouchDB.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-type" content="application/xhtml+xml; charset=utf-8"/>
<title>My Todos</title>
<script src="../script/couch.js" type="text/javascript" charset="utf-8"></script>
<script src="../script/json.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
function $(e)
{
return document.getElementById(e);
}
var Couch_Todo =
{
_todos:{},
db: new CouchDB("todo"),
insert: function(todo)
{
// DOM-fu
var list_item = document.createElement("li");
list_item.innerHTML = todo.text + ' <a href="#" onClick="Couch_Todo.remove(this.parentNode); return false;">X</a>';
$("todos").insertBefore(list_item, $("todos").firstChild);
this._todos[todo._id] = todo; // save todo object, so we can access the _rev when we want to delete.
list_item.id = todo._id; // save new id in DOM object
this.updateEmpty();
},
add: function(todo)
{
//automatically save time
todo.time = (new Date()).getTime();
this.db.save(todo);
this.insert(todo);
},
load:function()
{
var all_docs = this.db.view("todo/all");
if(all_docs && all_docs.total_rows > 0) {
for(var idx = 0; idx < all_docs.total_rows; idx++) {
var todo = all_docs.rows[idx];
this.insert({
_id:todo.id,
_rev:todo.value.rev,
text:todo.value.text
});
}
}
},
remove:function(li)
{
var doc = this._todos[li.id];
this.db.deleteDoc(doc);
this._todos[li.id] = null;
$("todos").removeChild($(li.id));
this.updateEmpty();
// focus the input field
$("todo").focus();
},
updateEmpty: function()
{
//if there are no todos, show a message
$("empty").style.display = (0 == $("todos").childNodes.length)?"block":"none";
}
}
window.onload = function()
{
// load saved todos
Couch_Todo.load();
// what to do when hitting enter in the input field
$("add").onsubmit = function()
{
var value = $("todo").value;
$("todo").value = ""; // reset input box
if(value) { // don't add empty todos
Couch_Todo.add({text:value});
}
// don't submit the form
return false;
}
// focus the input field when the document loads
$("todo").focus();
}
</script>
</head>
<body id="index">
<form id="add" action="">
<input type="text" name="todo" value="" id="todo"/>
</form>
<ul id="todos"></ul>
<div id="empty">Enter todo items in the box above and hit enter.</div>
</body>
</html>
Nice read.
Expanding this to include an Edit feature would be fun too :)
Thanks.
Good one.. "Edit" needs be done..
Minor typo: the port number in the URL near the top, http://localhost:5894/_utils/CouchDbToDo/, should be 5984.
Good catch. Fixed, thanks!
Also, if you want to source the couch.js and json.js files, you should append "../script/" to each in the src attribute.
Heh, good thinking! No need to have duplicate files around. Thanks again!
Nice example, made CouchDB usage clearer to me. But the list in a browser will not be notified about changes initiated from another browser. Could you elaborate on such concurrent updates and how to keep the views in sync? Thanks, Herman
I’m planning a follow up item here and I’ll look into adding this, but I don’t want to promise anything at this point.
But thanks for the suggestion!
Jan
Ok, the _rev seems to be stored inside the _todos-object when created. But when I reload the page, the delete fails. I’m not that skilled in javascript .. where does it fail??
This is really a great tutorial and it gives me a kickstart with CouchDb. Thanks a lot.
@Stefan Do you get any error messages on the Javascript Console? Do you use Firefox if not, do. If yes, make sure to install Firebug for debugging JS.
When I breakpoint at line this.db.deleteDoc(doc);
and examine doc: rev is already undefined and even "alldocs" doesn’t have the _rev attribute Is it possible that the view is not returning the _rev-element?
Indeed a good find here. Yes, unless you put it in, a normal view won’t contain the _rev attribute. I’ll need to clear that up. Thanks!
Hi Jan,
it works for me, when I use:
this.insert({ id:todo.id, _rev:todo.value.rev,
text:todo.value.text
});
instead of this.insert({ _id:todo.id, _rev:todo.value.rev,
text:todo.value.text
});
In the "load"-function. Hmm, could be a little bit more consistent (but this means, that the "normal" view does include "rev" ehhh resp. "_rev" ;-)). But anyway. Very nice stuff.
I’m also having this issue. The fix in #4.1.1.1.1.1 didn’t seem to show any difference between the before and after. Any thoughts as to why reloading breaks this?
Sorry, no time to investigate this at the moment.
I figured it out. There was a correction in that fix, but the problem was that the CouchDB server was not providing the _rev. This can be fixed by going into the CouchDB admin and updating _design/todo to become:
Thats crazy but I like it just like a crazy girl is sexy.
Thanks for this detailed example.
I get an error trying to delete items:
DELETE http://lamp.local:5984/todo/DD8E21F2DE6121FE8AA16875171BFEF3?rev=undefined 412 couch.js (line 67)
{"error":"conflict","reason":"2185125934"}
You need to send the revision, too. See the docs
I ran into errors with this tutorial and it took awhile to figure out that the views implementation has changed. For the first views example, "_design/todo/all", the following changes will correct the outdated syntax:
1) Use "javascript" instead of "text/javascript" for the language field.
2) Define a nested "map" property inside the "all" property. Assign the function to the "map" property.
3) Use emit(key, value) instead of amp(key, value) in the views function.
{ "all": { "map": "function(doc) {emit(null, {text:doc.text});}" } }
Good call, see http://wiki.apache.org/couchdb/BreakingChanges for updates.
I was trying to create your sample but I thought in the video demo (at Rackspace) that the index.html page was actually stored in couchdb. Since couchdb "stores documents" tried creating an "index.html" document and creating a key "contents" with HTML values, however the Web interface didn’t think it was valid JSON (even when I put quotes, though I didn’t \ every line).
Using the python couchdb module (and """ the string) I ended up with an error;
httplib2.RelativeURIError: Only absolute URIs are allowed. uri = localhost:5984/sample_db/index.html
So not sure it’s really a bug but I thought maybe it was something people would want to do…
You need to put a verbatim HTML file into an attachment to a document and point your browser to the attachment’s URL. E.g.:
http://server/db/app/index.html where
app
is a document and has an attachment index.html.Thanks Jan, I’d forgotten about attachments!
I can’t quite figure out how to do what you said though. I created a DB and a Doc and then created a key "_attachments" but pasting in HTML doesn’t seem to work.
There’s no "upload attachment" button or anything on the web interface so that was the best thought I had.
Thanks again!
Get trunk. And check out http://incubator.apache.org/couchdb/community/lists.html fro general CouchDB support.
I created DB "sample" and document "main" Then added an "_attachments" field with a value of;
{ "index.html": { "content_type" : "application/xhtml+xml; charset=utf-8", "data": "My Todos HI" } }
Then went to; http://192.168.1.99:5984/sample/main/index.html
But got; {"error":"not_found","reason":"missing"}
Interestingly; http://192.168.1.99:5984/sample/main/attachment_doc/index.html
Yielded a stranger (and longer) error message.
Nice tutorial, CouchBD is pretty cool. One thing that might not be evident is that a ‘todo’ database have to be created for the example to work.
Excellent tutorial Jan. Thanks man!
I learned a lot about views from this article Thanks
I’ve updated the application as per the comments best I’ve figured.
If I run the HTML from a home directory ~/todo/todo.html I get the error
Access to restricted URI denied" code: "1012 [Break on this error] req.open(method, uri, false);
I’ve used the full path to: /usr/local/share/couchdb/www/script/couch.js & json2.js
I’ve also tried running the html from an attachment in the database:
[Tue, 17 Nov 2009 21:18:46 GMT] [info] [] 127.0.0.1 - - ‘GET’ /usr/local/share/couchdb/www/script/json2.js 404
[Tue, 17 Nov 2009 21:18:46 GMT] [debug] [] httpd 404 error response: {"error":"notfound","reason":"nodb_file"}
so, I’m likely missing something basic and obvious!
I’m running version: {"couchdb":"Welcome","version":"0.11.0b830254"}
Looks like CouchDB has evolved and this tutorial now has a few bugs in it, (even with the bit rot it’s still a good first tutorial, I learned a lot figure out what was going wrong):
First: you should include "json2.js" (rather than "json.js")
Second: the "_design/todo" document should have the language "javascript" (at least that’s what the view editor in CouchDB admin tool does)
Third: the view "all" should refer to an object something like this "all": { "map": "function(doc) { emit(doc.time, {text: doc.text, rev: doc._rev}); }" }