Building a Blog Application with CouchDB
This article is part of our Academy Course titled CouchDB – Database for the Web.
This is a hands-on course on CouchDB. You will learn how to install and configure CouchDB and how to perform common operations with it. Additionally, you will build an example application from scratch and then finish the course with more advanced topics like scaling, replication and load balancing. Check it out here!
Table Of Contents
1. Introduction
The current chapter is an effort to build a blog application using the Javascript based Web Server Node.js along with CouchDB.
To make this application, we have selected:
- The swig client side javascript template engine (Refer to http://paularmstrong.github.io/swig/ for more documentation about this)
- Node.js for server side development
- Middleware handling with express.js
- CouchDb as the database
- Node.js Cradle Module Extension (to make communication with CouchDB)
Since this lessons is primarily about couchdb, we will discuss the CouchDB related functionalities in detail. We will also discuss about the related blog application functionality.
Please refer to the couchdb-blog.zip file for the source code of the application.
Node.js is a javascript runtime. Unlike traditional web servers, there is no separation between the web server and the code, and we do not have to customize configuration files (XML or Property Files) to get the Node.js Web Server up. With Node, we can create the web server with minimal code and deliver content with the code. We will describe in this lesson how to create a web server with Node and how to work with static and dynamic file content. Additionally, we will talk a bit about performance tuning in the Node.js Web server.
We have used the Node.js server for the web controller and the routing of the contents. The persistence and fetching of the data will be done through CouchDB – a package for couchdb handling (cradle) will be installed through NPM (Node Package Manager Registry). The front end rendering will be performed by Swig (a JavaScript Template Engine), that also is installed with the node package manager consolidated module (swig comes with this).
Below is the app architecture:
In the application directory (see attached file), the “package.json” can be found. Additionally, all the node.js library dependencies are also there.
To run all the required libraries, we need to run “npm install” in the terminal. We need to start the application using node app.js
2. The App.js functionality
var express = require('express') , app = express() // Web framework to handle routing requests , cons = require('consolidate') // Templating library adapter for Express , routes = require('./routes'); // Routes for our application var cradle = require('cradle'); // Driver for node.js Couchdb Driver
The above code is used for the initialization of the libraries.
Following are the database credentials. Please note that before running the code below, the database must be present (i.e. to be created in couchdb). We can easily accomplish this with futon (couchdb administrative console):
var databaseUrl = "blogdb"; var connection = new(cradle.Connection)('http://piyas:secure2013@localhost', 5984, { auth: { username: 'piyas', password: 'secure2013' } }); var db = connection.database(databaseUrl);
Below is the code for registering the swig templating engine in node.js express middleware.
app.engine('html', cons.swig); app.set('view engine', 'html'); app.set('views', __dirname + '/views');
3. Notes about the Express.js module
Express.js is a powerful web development framework for the Node.js (Node) platform. It comes with the Node.js middleware modules. These components are JavaScript components which can be used in Express.js based web applications to make the application modular and structured in layers.
With express.js, other node.js core APIs can also be called except for the express.js apis. The express.js framework can be used to develop any kind of web application – simple to complex. With Express.js development, we have to keep the asynchronous behavious of the application in mind.
4. Express.js Objects
4.1. The application object
The application object is an instance of Express, generally presented by the variable named ‘app’. This is the main object of our Express application. All of the application functionality is built using this object.
Following, we create an instance of the Express.js module within the node application:
var express = require('express');
4.2. The request object
Now, when a web client makes a request to the Express application, the HTTP request object is created. All the callbacks in the application, where the request objects are passed as reference, are represented with a conventional variable ‘req’. This request object holds all the HTTP stack related variables, such as header informations, HTTP methods and related properties for a particular request from the web client.
Below we present some methods of the Request Object which are important in our web application development:
- req.params: Holds the values of all the parameters of the request object
- req.params(name): Returns the value of a specific parameter from the GET params or POST params
- req.query: Takes values of a GET method submission
- req.body: Takes values of a POST form submission
- req.get(header): Gets the request HTTP header
- req.path: The request path
- req.url: The request path with query parameters
4.3. The response object
The response object is created along with the request object and is generally represented by a variable named ‘res’. In the HTTP Request-Response model, all the express middleware functions work on the request and the response object, while passing the control one after another.
Some methods of the Response Object which are important in our web application development –
- res.status(code): The HTTP response code
- res.attachment([filename]): The response HTTP header Content-Disposition to attachment
- res.sendfile(path, [options]): Sends a file to the client [callback]
- res.download(path, [filename]): Prompts the client to download from [callback]
- res.render(view, [locals]): Renders a view callback
5. Concepts used in Express
5.1. Asynchronous JavaScript
Node.js programming is mainly done with Asynchronous Javascript Programming. All of the modules in node.js are built based on an asynchronous nature. So, the execution of code from one layer to another generally occurs within callback functions. Node and Express are built on the concept of async operations, and all the results are handled in callback functions.
As a node.js program executes in an event loop, the end user generally does not have to wait for a response from the view layer i.e web browser or mobile browser etc. Generally, the callback function is passed to an async function to be executed and this returns the result to an upper function, when the execution of code is completed within the callback function.
All the programs within express.js and associated programs are installed on the node.js environment as node modules. For any node.js application, the deployment configurations are written in package.json file. If we need to install the application as a node module in the node.js environment, i.e. through the npm install command, we should include the package.json file.
5.2. Middlewares in node.js applications
A middleware in node.js application context is a JavaScript function to handle HTTP requests to an Express.js application. It will be able to handle the request and the response objects from the HTTP request, perform some operation on the request, send the response to the client and will be able to pass the objects/results to the next middleware.
Middlewares are loaded in an Express application with app.use() method.
A basic example of a middleware can be for a GET method of a request object, as follows:
app.use(express.cookieParser()); app.use(express.bodyParser());
The majority of the Express.js functionality is implemented with its built-in middlewares. One example of an Express.js middleware is the router middleware, which is responsible for routing the HTTP requests to Express applications and to the appropriate data handler functions. From a user perspective, it is the navigational functionality in a web application.
The destinations of the HTTP request URIs are defined via routes in the application. Routes are the controlling points for the response from a request, i.e they decide where to route a specific request by analysing the data in the request object. In traditional web application, like in a J2ee Application, this functionality is handled by the Controller in the application. Route handlers may be defined in the app.js file or loaded as a Node module.
Now let’s see the routes function:
// Application routes routes(app, db);
This code resides by default in the index.js file (inside the routes folder).
We can see the code in index.js:
app.get('/newpost', contentHandler.displayNewPostPage);
So, whenever the application gets a request with ‘/newpost’, it will go to the displayNewPostPage function of the contentHandler function i.e. in the content.js file.
Now if we can go to displayNewPostPage function, we can see:
this.displayNewPostPage = function(req, res, next) { "use strict"; if (!req.username) return res.redirect("/login"); return res.render('newpost_template', { subject: "", body: "", errors: "", tags: "", username: req.username }); }
Here, if the ‘username’ variable is not present in request object, the application will redirect the user to the ‘login’ action. Otherwise, the response object will render the template ‘newpost_template’, which will show the html file ‘newpost_template.html’ in ‘views’ folder. Almost all the web flow executes in a similar way in this application. All the web flows are documented within ‘index.js’ of ‘routes’ folder.
Now we configure the application to listen in a specific port using the following code:
app.listen(8082);
5.3. Works in index.js
Generally the index.js contains all the controller functionality. Let’s see the following example:
app.post('/newpost', contentHandler.handleNewPost);
Here the function in content.js is the following:
this.handleNewPost = function(req, res, next) { "use strict"; var title = req.body.subject var post = req.body.body var tags = req.body.tags if (!req.username) return res.redirect("/signup"); if (!title || !post) { var errors = "Post must contain a title and blog entry"; return res.render("newpost_template", {subject:title, username:req.username, body:post, tags:tags, errors:errors}); } var tags_array = extract_tags(tags) var escaped_post = sanitize(post).escape(); var formatted_post = escaped_post.replace(/\\r?\\n/g,'<br>'); posts.insertEntry(title, formatted_post, tags_array, req.username, function(err, permalink) { "use strict"; if (err) return next(err); //redirect to the blog permalink return res.redirect("/post/" + permalink) }); }
Note that the request object values are taken in the function through the ‘req’ object. All the required validations are also performed. Then, the ‘insertEntry’ function of posts.js will be called to save the data. This function will be described later.
All the functions related to couchdb database handling are explained below.
6. CouchDB Handling
Before we are able to fetch data from CouchDB, we will need to create the appropriate views for CouchDB key,value stores. Please refer to our previous douments for view creation and map function creation in couchdb.
Before working on the application we have to create the following views:
- To access user data through user name, we have to create the following design document in the ‘blogdb’ database:
db.save('_design/user', { views: { byUsername: { map: 'function (doc) { if (doc.type === "user") { emit(doc.username, doc) } }' } } });
This will create the map function with a key as username and a value as document.
- To access user data specific to one session, we have to create the following design document in the ‘blogdb’ database:
db.save('_design/session', { views: { bySessionid: { map: 'function (doc) { if (doc.type === "session") { emit(doc._id, doc) } }' } } });
- To access posts in the couchdb database, we have to create the following design document in the ‘blogdb’ database:
db.save('_design/post', { views: { byPosts: { map: 'function (doc) { if (doc.type === "post") { emit(doc._id, doc) } }' }, byTags: { map: 'function (doc) { if (doc.type === "post") { emit(doc.tags, doc) } }' }, byPermalinks: { map: 'function (doc) { if (doc.type === "post") { emit(doc.permalink, doc) } }' } } });
Note the following:
- ‘byPosts’ function will create ‘_id’ as the key and whole document as value.
- ‘byTags’ function will create ‘tags’ as the key and whole document as value.
- ‘byPermalinks’ function will create ‘tags’ as the key and whole document as value.
Now let’s discuss the main functions for CouchDB handling, invoked through cradle:
6.1. Reference – posts.js
- function PostsDAO(db) – We can have a reference of the Database through this constructor (db).
- Insert the post (Creation of the Document, simple json Document):
var post = {"title": title, "author": author, "body": body, "permalink":permalink, "tags": tags, "comments": [], "type":"post", "date": new Date()}
We handle the document persistence through cradle:
// insert the post db.save('POSTID_'+Math.random(), post, function (err, res) { if (err) { // Handle error res += ' SAVE ERROR: Could not save record!!\\n'; callback(err, null); } else { // Handle success res += ' SUCESSFUL SAVE\\n'; callback(err, permalink); } });
The first argument to the ‘save’ function expects a unique id field. The second argument is the ‘post’ variable. The third argument is the handle of a function for the response. This will return the callback to the event or functionality with or without error.
- Get all the posts (Function name: getPosts):
db.view('post/byPosts', { }, function (err, doc) { console.dir(doc); "use strict"; if (err) return callback(err, null); console.log("Found " + doc.length + " posts"); callback(null, doc); });
The first argument is the design document name. The second argument is the combination of the key and the value by which the document will be accessed. If it is empty json, then it will return all the documents. The third argument is the handle of a function with returned document/documents.
- Get all Documents by tags array (Function name: getPostsByTag):
db.view('post/byTags', { key : tagArray }, function (err, doc) { console.dir(doc); "use strict"; if (err) return callback(err, null); console.log("Found " + doc.length + " posts"); callback(null, doc); });
The first argument is the design document name. The second argument is the combination of key and value by which the document will be accessed. The third argument is the handle of a function with returned document/documents.
- Get all Documents by permalinks (Function name: getPostByPermalink):
db.view('post/byPermalinks', { key : permalink }, function (err, doc) { console.dir("in permalink --" + doc); "use strict"; if (err) return callback(err, null); console.log("Found " + doc.length + " posts"); callback(null, doc); });
The first argument is the design document name. The second argument is the combination of key and value by which the document will be accessed. The third argument is the handle of a function with returned document.
6.2. Reference – sessions.js
- function SessionsDAO(db) – The reference of the Database is done through this constructor (db).
- save the doucment for Session (Function name: startSession) – Creating a session id:
var session_id = crypto.createHash('sha1').update(current_date + random).digest('hex');
Creating as session document:
var session = {'username': username, '_id': session_id,'type':'session'}
Saving a document:
db.save(session_id, session, function (err, res) { console.log(err); if (err) { // Handle error res += ' SAVE ERROR: Could not save record!!\\n'; } else { // Handle success res += ' SUCESSFUL SAVE\\n'; } console.log('session start.2'+res); callback(err, session_id); });
- get username for a session (Function name: getUsername):
db.view('session/bySessionid', { key: session_id }, function (err, doc) { console.dir(doc); "use strict"; if (err) return callback(err, null); if (!doc) { callback(new Error("Session: " + doc + " does not exist"), null); return; } callback(null, doc[0].value.username); });
The first argument is the design document name. The second argument is the combination of key and value by which the document will be accessed. The third argument is the handle of a function with returned document.
6.3. Reference – users.js
- Adding a user (Function name: addUser):
var user = {'email': email, 'password': password_hash, 'username': username,'type':'user'};
Saving the document in CouchDB:
db.save('ID_'+Math.random(), user, function (err, res) { if (err) { // Handle error res += ' SAVE ERROR: Could not save record!!\\n'; } else { // Handle success res += ' SUCESSFUL SAVE\\n'; } return callback(err, null); });
- validate a user (Function name: validateLogin):
db.view('user/byUsername', { key: username }, function (err, doc) { if (err) return callback(err, null); if (doc) { if (bcrypt.compareSync(password, doc[0].value.password)) { console.log('here we are'); callback(null, doc); } else { var invalid_password_error = new Error("Invalid password"); // Set an extra field for any error which is not a db error invalid_password_error.invalid_password = true; callback(invalid_password_error, null); } } else { var no_such_user_error = new Error("User: " + user + " does not exist"); no_such_user_error.no_such_user = true; callback(no_such_user_error, null); } });
The first argument is the design document name. The second argument is the combination of key and value by which the document will be accessed.The third argument is the handle of a function with returned document.
To summarize, in this article, we have talked about:
- Running an application in Node.js web server
- A minimal routing for data handling
- Handling data from a GET request
- Handling data from a POST request
- Handling CouchDB functions through cradle
- View creation and data access in couchdb
There are rooms for improvement for the node.js code, like adding blog comment support, improving the business logic, like handling duplicate post, adding a category for the blog etc. These enhancements are left as an exercise for the reader.
You may download the source code here.
This blog was… how do you say it? Relevant!! Finally I’ve found something which helped me. Cheers!
The article was absolutely fantastic! Lot of great information which can be helpful in some or the other way. Keep updating the blog, looking forward for more contents.