Blog rolling with CouchDB, Express and Node.js

07 Feb 2011

Over the last little while, I’ve been doing a lot of playing with Node.js, mostly to run data collection scripts. Last week, I started following Ciaran Jessup’s tutorial on getting started with node.js, Express and mongoDB. Express acts as a framework to node.js, allowing you to work in a familiar mvc format in a not so familar server side language. I hit a few problems along the way in the tutorial, so I thought I’ve list a few of my findings here. I also wanted to make use of my preferred flavour of nosql – couchdb with express, which proved extremely easy to port the mongo model over to it. I hope someone out there finds this useful as I’ve yet to find a vast community using couchdb/express.

First things first, you’ll want to install Node and npm (the node package manager) in order to be able to easily install node packages. You’ll also probably find it handy to have the original tutorial open alongside this one. I’ve been using the latest versions of node (0.3.7) and npm (0.2.17) at the time of writing.

Once that’s done, grab copies of the packages that we’ll be using:

npm install express
npm install jade
npm install sass

If you want to use couchdb, then make sure you have it installed – grab it over here and then install the package for talking to node.

npm install cradle

The first hurdle I found was that the way in which that express is called has changed a little.

var express = require('express');

var app = express.createServer();

app.configure(function(){
  app.use(express.methodOverride());
  app.use(express.bodyDecoder());
  app.use(express.logger());
});

app.get('/', function(req, res){
    res.send('Hello World');
});

app.listen(3000);

Then by saving to a file called app.js and calling using:

node app.js

Once that’s done. You can then visit 127.0.0.1:3000 in your browser to see a rivetting message!

After creating folder beneath our original app.js in which to put views, you can use the original article provider file and the updated app.js below in order to have an app with a few articles shown.

var express = require('express');

var ArticleProvider = require('./articleprovider-memory').ArticleProvider;

var app = express.createServer();

app.configure(function(){
  app.use(express.methodOverride());
  app.use(express.bodyDecoder());
  app.use(express.logger());
});

var articleProvider= new ArticleProvider();

app.get('/', function(req, res){
  articleProvider.findAll(function(error, docs){
    res.send(require('sys').inspect(docs));
  })
});

app.listen(3000);

Now when you re-run, you’ll see 3 separate articles. Oooh fancy!

Next I hit my first hurdle. Express no longer uses the HAML HTML template language and instead uses JADE by default. This requires converting the HAML templates across to their equivalent JADE counterparts. Basically, this is as simple as dropping the ‘%’ from the beginning of each line (I also replaced braces with brackets in later templates).

html
  head
    title= title
    link(rel: 'stylesheet', href: '/style.css' )
  body
    #wrapper
      != body
h1= title
#articles
  - each article in articles
    div.article
      div.created_at= article.created_at
      div.title= article.title
      div.body= article.body

The app.js now becomes:

var express = require('express');

var app = express.createServer();

app.configure(function(){
  app.set('view engine', 'jade');
  app.set('views', __dirname + '/views');
  app.use(express.methodOverride());
  app.use(express.bodyDecoder());
  app.use(express.logger());
  app.use(app.router);
  app.use(express.compiler({ src: __dirname + '/views', enable: ['sass'] }));
});

var ArticleProvider = require('./articleprovider-memory').ArticleProvider;

app.get('/', function(req,res){
  articleProvider.findAll(function(error, docs){
    res.render('blogs_index.jade', {
      locals: {
        title: 'Blog',
        articles: docs
      }
    });
  })
})

app.get('/*.css', function(req,res){
  res.render(req.params[0] + '.css.sass', { layout: false });
});

app.listen(3000);

You’ll notice that here we enable the CSS compiler sass and HTML compiler jade. If you download the original sass CSS template into your views folder, you can now restart the app and inspect the fruits of your labour. The CSS shouldn’t actually sit in the views folder, according to the creator of Express, and should instead should be compiled with the sass package itself. I’ve yet to discover the correct way of doing this. To request a stylesheet in the view, we need to do the following:

html
  head
    title= title
    link(rel: 'stylesheet', href: '/style.css' )
  body
    #wrapper
      != body

If you reload now and visit 127.0.0.1:3000 you should see your posts with a little more style.

Creating a form for new posts looks like this:

h1= title
form( method= 'post' )
  div
    div
      span Title :
      input(type='text', name= 'title', id= 'editArticleTitle' )
    div
      span Body :
      textarea( name= 'body', rows= 20, id= 'editArticleBody' )
    div#editArticleSubmit
      input( type= 'submit', value= 'Send' )

And the new app.js routes are as follows:

app.get('/blog/new', function(req,res){
  res.render('blog_new', {
    locals: {
      title: 'New Post'
    }
  });
});

app.post('/blog/new', function(req,res){
  articleProvider.save({
    title: req.param('title'),
    body: req.param('body')
  }, function(error, docs) {
    res.redirect('/')
  });
});

In order to add persistence using mongodb, nothing changes in the original model file, so go ahead and use that. You’ll need to have installed the package ‘mongodb’ if you’d like to try out using it though and update your instantiation of the articleprovider class by supplying a port number to which mongo is installed.

Adding CouchDB Persistence

Here, I took my own angle on the tutorial and decided to give attempting to make my own persistence model using couchdb a go. It proved to be extremely easy, given the JSON representation and HTTP/GET method of access already built in to it.

var cradle = require('cradle');

ArticleProvider = function(host, port) {
  this.connection= new (cradle.Connection)(host, port, {
    cache: true,
    raw: false
  });
  this.db = this.connection.database('articles');
};

ArticleProvider.prototype.findAll = function(callback) {
    this.db.view('articles/all',function(error, result) {
      if( error ){
        callback(error)
      }else{
        var docs = [];
        result.forEach(function (row){
          docs.push(row);
        });
        callback(null, docs);
      }
    });
};

ArticleProvider.prototype.findById = function(id, callback) {
    this.db.get(id, function(error, result) {
        if( error ) callback(error)
        else callback(null, result)
      });
};

ArticleProvider.prototype.save = function(articles, callback) {
    if( typeof(articles.length)=="undefined")
      articles = [articles];

    for( var i =0;i< articles.length;i++ ) {
      article = articles[i];
      article.created_at = new Date();
      if( article.comments === undefined ) article.comments = [];
      for(var j =0;j< article.comments.length; j++) {
        article.comments[j].created_at = new Date();
      }
    }

    this.db.save(articles, function(error, result) {
      if( error ) callback(error)
      else callback(null, articles);
    });
};

exports.ArticleProvider = ArticleProvider;

I added the following view and route to my app.js too, to allow support for clicking upon articles.

div.article
  h1= article.title
  div.created_at= article.created_at
  div.body= article.body
app.get('/blog/view/:id', function(req,res){
   articleProvider.findById(req.params.id,
     function(error, doc){
       res.render('blog_view', {
       locals: {
         title: 'New Post',
         article: doc
       }
     });
  });
});

And in order to support it, the main view now becomes as follows:

h1= title
#articles
  - each article in articles
    div.article
      div.created_at= article.created_at
      - var articlelink = 'blog/view/' + article._id;
      a(href=articlelink)
        div.title= article.title
      div.body= article.body

  a(href='blog/new')
    Add new post

Anyway, so far I’ve not yet added comment support, but given the headway I made here, I’d imagine it would be extremely easy to integrate into my couchdb article model. I’ll update here if I ever get round to adding it!

Tagged couchdb, expressjs, mongdb, node, nodejs,
comments powered by Disqus