Connecting and Working with CouchDB with Node & Express

There are many interesting NoSQL solutions and more are created almost daily. One of the most popular is CouchDB.

One of its main strengths is that it uses HTTP as its communication protocol which makes it very easy to use from any programming language.

CouchDB is also the primary store behind NPM. We have also used CouchDB for many of our projects. For example Mehana is build entirely on CouchDB.

As you grow, your needs grow. You need more than just to connect to your database and get some stuff. You need to add some business specific logic.

In this article we will look at how to connect to your CouchDB instance and then we will go all the way to building models which implement your business logic.

Installing

Before we begin you need to go to couchdb.apache.org to download and run the latest version of CouchDB. Follow the instructions and you will be ready in less than 5 minutes.

CouchDB has a really nice and simple HTTP protocol, so we are not going to use any CouchDB specific library. Instead we are going to use request which is a library to easily make any http requests.

$ npm install request

You are ready to begin. However, if you want to use a specific CouchDB library you could do that with nano or cradle.

Once CouchDB is running you should be able to access its admin interface at http://127.0.0.1:5984/_utils/index.html.

Connecting

Unlike other databases CouchDB doesn’t keep open connections. Instead each requests opens a new http connection.

Let’s have a look at how that works.

var request = require('request')

var url = 'http://127.0.0.1:5984/'
var db = 'mydatabase/'
var id = 'document_id'

// Create a database/collection inside CouchDB

request.put(url + db, function(err, resp, body) {
  // Add a document with an ID

  request.put({
    url: url + db + id,
    body: {message:'New Shiny Document', user: 'stefan'},
    json: true,
  }, function(err, resp, body) {
    // Read the document

    request(url + db + id, function(err, res, body) {
      console.log(body.user + ' : ' + body.message)
    })
  })
})

This is a very simple example. It creates a database for documents inside CouchDB. Then inserts a document and finally it reads it back and displays the result.

We are leaving out all error checking for the time being.

If you try to run this script multiple times it will not work. CouchDB does not allow multiple databases with the same name. It also doesn’t allow multiple documents with the same ID.

To run the code from above, you can put it in index.js and then do the following.

$ node index.js

If CouchDB is running and you have installed the NPM package you should see as a result something like the line below.

stefan : New Shiny Document

Base App

Let’s assume that we have a web application with the following structure.

app/
  controllers/
    comments.js
    users.js
  models/
  views/
  app.js

It will be an Express application with MVC structure.

Reusing Common Patterns

As I already mentioned it, CouchDB is so simple to use that you don’t actually need separate library. Most of the time you can just use request and do http requests.

However, we don’t want to write the same code over and over again for some common tasks. We also don’t want to pass configuration around. It is much better to have it at only one place.

To solve that let’s create a db.js file which will contain our small library for working with our CouchDB.

var request = require('request')
  , querystring = require('querystring')

var url = 'http://127.0.0.1:5984'

// Save a document

exports.save = function(db, doc, done) {
  request.put({
    url: url + '/' + db + '/' + doc._id,
    body: doc,
    json: true,
  }, function(err, resp, body) {
    if (err) return done('Unable to connect CouchDB')
    if (body.ok) {
      doc._rev = body.rev
      return done(null doc)
    }

    done('Unable to save the document')
  })
}

// Get all documents with the built-in 'All' view

exports.all = function(db, options, done) {
  var params = querystring.stringify({
    include_docs: options.include_docs === false ? false : true,
    descending: options.descending,
    skip: options.skip,
    limit: options.limit,
    key: options.key,
    startkey: options.startkey,
    endkey: options.endkey,
  })

  request({
    url: url + '/' + db + '/_all_docs?' + params,
    json: true,
  }, function(err, res, body) {
    if (err) return done('Unable to connect to CouchDB')
    done(null, body)
  })
}

For our example we only need two methods. One to save a document, either a new one or an existing one. Another to read all documents from the database by using the built-in ‘All Documents’ view which can give us what we need.

In a complex app we will have a few more methods including some which will help us to better test the application.

Let’s have a look at how we are going to use it in one of the controllers. We are going to implement comments.js from the file structure above.

var express = require('express')
  , router = express.Router()

var db = require('../db')

// Submit a comment

router.post('/submit', function(req, res) {
  var data = {
    _id: (new Date().toJSON()) + ':' + req.session.user._id
    message: req.body.comment,
  }

  db.save('comments', data, function(err, doc) {
    res.redirect('/all')
  })
})

// Get all comments

router.get('/all', function(req, res) {
  db.all('comments', {}, function(err, data) {
    res.render('comments', {comments: data.rows})
  })
})

// Get most recent comments

router.get('/recent', function(req, res) {
  db.all('comments', {limit: 20, descending: true}, function(err, data) {
    res.render('comments', {comments: data.rows})
  })
})

module.exports = router

The built in ‘All Documents’ view returns by default all documents ordered by their id.

Each comment id begins with the date and then the user id. As a result the comments are automatically sorted by date because the toJSON returns the date in a format easily sortable alphabetically.

That is why /recent works. It limits to 20 comments and because they are by default in ascending order, we read the comments from back to front and the result is exactly what we want.

We can implement the user controller in a similar way with those two methods.

If you try the example above right now it will not work. You need first to create the comments database. For this example you can create it from the admin console at http://127.0.0.1:5984/_utils/index.html

Building models with business logic

Reusing our small database library is useful, but we can further improve how we work with CouchDB. For example, you may need to get all comments for many different tasks like displaying them or running some statistics on them or do something else.

When you later want to make a change to how you work with your database, you will have to make the change everywhere.

But this is not the only problem. The way it works now, requires that you controllers know database specific details.

To improve this lets go full Model-View-Controller pattern and implement some models.

app/
  controllers/
    comments.js
    users.js
  models/
    comments.js
    users.js
  views/
  app.js

As you can see we will have two more files in the models folder with content looking like the following

var db = require('../db')
  , DATABASE = 'comments'

exports.submit = function(user, comment, done) {
  var data = {
    _id: (new Date().toJSON()) + ':' + user
    message: comment,
  }

  db.save(DATABASE, data, function(err, doc) {
    if (err) return done('Unable to connect to CouchDB')
    if (doc.error) return done('Unable to save the comment')
    done(null, doc)
  })
}

exports.all = function(done) {
  db.all(DATABASE, {}, function(err, data) {
    if (err) return done('Unable to connect to CouchDB')
    if (data.error) return done('Unable to get the comments')
    done(null, data.rows)
  })
}

exports.recent = function(done) {
  db.all(DATABASE, {limit: 20, descending: true}, function(err, data) {
    if (err) return done('Unable to connect to CouchDB')
    if (data.error) return done('Unable to get the comments')
    done(null, data.rows)
  })
}

Our comment model uses what we already implemented in our database library and hides the implementation from our controllers. It just provides a few useful methods like submit, all and recent which are also easier to understand when you read the controller.

Let’s have a look at how our controller will look like after we introduce the model.

var express = require('express')
  , router = express.Router()

var Comments = require('../models/comments')

// Submit a comment

router.post('/submit', function(req, res) {
  Comments.submit(req.session.user._id, req.body.comment, function(err, doc) {
    res.redirect('/all')
  })
})

// Get all comments

router.get('/all', function(req, res) {
  Comments.all(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

// Get most recent comments

router.get('/recent', function(req, res) {
  Comments.recent(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

module.exports = router

At the end our models share the database management file db.js and they are the only ones who are aware how we are requesting our data from CouchDB.

Next

We have just barely scratched the surface of how you can work with CouchDB. For example, we have talked very little on how different errors should be handled and the code above is missing most error handling.

We also didn’t mention anything about using and defining views, one of the core features of CouchDB.

There is much more, but with the base structure from above you should easily work with all other CouchDB features.


Other articles that you may like

Did you like this article?

Please share it
Enter your email and get our NPM Cheat Sheet for NodeJS Developers and the links to our 5 most popular articles which have helped thousands of developers build faster, more reliable and easier to maintain Node applications.

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2018 Terlici Ltd · Terms · Privacy