The aim of this tutorial is to create a Node powered web app using a CouchDB database and Haml based templates. I wanted something relatively simple that I could do in a couple of sittings, but complex enough to be of some actual use. In the end I opted for a “top 10 list” type application. The functionality being as follows:
- To list a number of items
- To remove an item from the list
- To add an item to the list
Obviously, before getting cracking with this tutorial I had to think up a name for this “killer app” of the Node world.
So I give you ErdNodeFlips! In honour of erdnussflips – the strangely addictive Wotsit type things that taste of peanut butter and stick to the top of your mouth. Yum! (OMG – they even have their own Facebook page!)
All code is available on GitHub.
Directory Structure and Libraries
The first step is to create a directory structure for the application and bring in the relevant libraries. We need two directories underneath the top-level application root: templates (which will hold the haml files) and libs (which will contain 3rd party libraries). We are going to grab two libraries off GitHub:
- Felix Geisendörfer’s node-couchdb – this is what we’ll be using to interact with the Couch database
- Tim Caswell’s haml-js – this is our haml template engine
$ cd / $ mkdir {libs,templates} $ cd libs $ git clone git://github.com/felixge/node-couchdb.git $ git clone http://github.com/creationix/haml-js.git $ cd ../ |
Basic Web Server
Now we can get started. Open up a new file called main.js in your favourite editor. Now, just to make sure that everything is working at this early stage you can copy and paste the http server example from the nodejs.org front page and run that.
http.createServer(function (req, res) { setTimeout(function () { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Worldn'); }, 2000); }).listen(8000); |
$ node ./main.js |
Navigate to http://127.0.0.1:8000/ and after 2 seconds you should see “Hello World” appear. If you see this, all is working. We have the (very basic) base of our application.
Talk to the Database
Once we know that our basic setup is working, we can try and interact with the database. Before we can do this, however, we actually need to create the database and add an record – or document.
CouchDB comes with a nifty little utility called Futon. We’ll use this to quickly create our database and add a document.
- Go to http://localhost:5984/_utils/
- Create a new database: erdnodeflips
- Create a new document
- Add Field – name it name
- Double click on the value for name, enter “Don’t forget” (including the quotes)
- Add another field – name it items
- Double click on the value for items enter: ["item 1", "item 2", "item 3"] (including quotes and square brackets)
- Click on Save Document – copy the _id value
- Navigate to: http://localhost:5984/erdnodeflips/[value of "_id"] to see something like: {“_id”:”2d36f401bc4b82c9160e1a4ea936aba3″,”_rev”:”2-3b9f738a50ff9928348ce32b0eb807b2″,”items”:["item 1","item 2","item 3"],”name”:”Rob’s List”}
To read this within node we simply need to know the _id of the document. Save this as a var for now:
var doc_id = ’2d36f401bc4b82c9160e1a4ea936aba3′;
and replace the setTimeout function with the following:
db.getDoc(doc_id, function(error, doc) { if(error) { res.write(JSON.stringify(error)); } else { res.write('Fetched my new doc from couch:'); res.write(JSON.stringify(doc)); } res.end("-= Fin =-"); }); |
This is pretty simple, and should be relatively straight forward to read. Essentially the code tries to get the document identified by doc_id, invoking a function when done: db.getDoc(doc_id, function(error, doc){});
The function that is invoked has 2 arguments, one containing the error (if any) and the second containing the actual document returned (if any).
Within this function we can then deal with the two arguments as needed. If there is an error, we output that to the browser, if no error and a doc is returned, output that instead.
Finally, we end the server response: res.end(“-= Fin =-”);. Note, we must end the server response within the callback of the getDoc() function. Due to the event driven, asynchronous nature of Node, if we end the response after that function then the server response will more than likely be closed before getDoc() has had a chance to write the additional server responses.
So the code now looks like this:
var sys = require('sys'), fs = require('fs'), http = require('http'); // Node-CouchDB: http://github.com/felixge/node-couchdb var couchdb = require('./libs/node-couchdb/lib/couchdb'), client = couchdb.createClient(5984, 'localhost'), db = client.db('erdnodeflips'); // Haml-js: http://github.com/creationix/haml-js var haml = require('./libs/haml-js/lib/haml'); var doc_id = '2d36f401bc4b82c9160e1a4ea936aba3'; http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); db.getDoc(doc_id, function(error, doc) { if(error) { res.write(JSON.stringify(error)); } else { res.write('Fetched my new doc from couch:'); res.write(JSON.stringify(doc)); } res.end("-= Fin =-"); }); }).listen(8000); sys.puts('Server running at http://127.0.0.1:8000/'); |
Generate Nicer Output
If you run the above code, you should get something along the lines of:
Fetched my new doc from couch:{“_id”:”2d36f401bc4b82c9160e1a4ea936aba3″,”_rev”:”2-3b9f738a50ff9928348ce32b0eb807b2″,”items”:["item 1","item 2","item 3"],”name”:”Rob’s List”}-= Fin =-
So it works, but it doesn’t look very good.
First, lets create a Haml template that we can use to output something better.
!!! Strict
%html(lang="en")
%head
%title&= title
%body
%h1&= title
%p&= message
:if items.length === 0
%p There are no items to remember
:if items.length > 0
%ul
:each item in items
%li&= |
I’m not going to go into great depth. You can find more information on Haml at the Haml homepage and also have a look at Tim’s excellent tutorial on his Haml-js library.
Essentially, this template is going to build an XHTML strict page that will have a title, a message and then should loop through the items in the list. To get this displayed, we then need to open up the template file, render it and then output it as a server response. The code to do that is:
fs.readFile('./templates/doc.haml', function(e, c) { var data = { title: "Erdnodeflip document: "+doc.name, message: "Your Erdnusflip document was found!", items: doc.items, }; var html = haml.render(c.toString(), {locals: data}); res.end(html); }); |
Notice that I am simply ending the response with the generated HTML. Again, it is worth keeping an eye on exactly where the response is being ended otherwise you might find you are trying to add to a conversation that has already ended.
We can do the same for the “document not found” route:
!!! Strict
%html(lang="en")
%head
%title&= title
%body
%h1&= title
%p&= message
%p %a{href: link}&= link_text |
fs.readFile('./templates/no-doc.haml', function(e, c) { var data = { title: "No Document Found", message: "No document could be found", link: "/create", link_text: "Create a new document" }; var html = haml.render(c.toString(), {locals: data}); res.end(html); |
Quick Recap for this Tutorial
In this tutorial we’ve only scratched the surface of what can be done. However, in a relatively short space of time we have our Node web server communicating with a CouchDB database and displaying some valid strict XHTML. Not bad for one sitting!
To recap, our code now looks like this:
var sys = require('sys'), fs = require('fs'), http = require('http'); // Node-CouchDB: http://github.com/felixge/node-couchdb var couchdb = require('./libs/node-couchdb/lib/couchdb'), client = couchdb.createClient(5984, 'localhost'), db = client.db('erdnodeflips'); // Haml-js: http://github.com/creationix/haml-js var haml = require('./libs/haml-js/lib/haml'); var doc_id = '2d36f401bc4b82c9160e1a4ea936aba3'; http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/html'}); db.getDoc(doc_id, function(error, doc) { if(error) { fs.readFile('./templates/no-doc.haml', function(e, c) { var data = { title: "No Document Found", message: "No document could be found", link: "/create", link_text: "Create a new document" }; var html = haml.render(c.toString(), {locals: data}); res.end(html); }); } else { fs.readFile('./templates/doc.haml', function(e, c) { var data = { title: "Erdnodeflip document: "+doc.name, message: "Your Erdnusflip document was found!", items: doc.items, }; var html = haml.render(c.toString(), {locals: data}); res.end(html); }); } }); }).listen(8000); sys.puts('Server running at http://127.0.0.1:8000/'); |
The exciting thing here is that we’ve thrown in a link! However, if you click on it, it will simply reload the page.
Being able to handle this kind of basic page navigation and actually adding stuff to the database is for next time.
Feedback always welcome. You can find all the code on my GitHub pages.
Valuable Node resources:
- The official Node JS site
- The Node Github page
- How To Node – a great place for tutorials and other tips
- Node Blogs – which appears to be down at the moment, but again is very useful
- NodeJS Google Group – keep your finger on the pulse
Pingback: » First Steps with Node.js: exciting stuff
Pingback: NodeJS Tutorial – Part 2, Routing
Pingback: Link dump for June 7th | The Queue Blog
Pingback: [digital:meditation]» Blog Archive » a clueless start to node.js