Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Create A Full Stack App Using Node.js & Couchbase Server

TwitterFacebookRedditLinkedInHacker News

Previously I wrote about how to create an AngularJS application with PouchDB and Couchbase. The way I demonstrated is of course one way to accomplish the job of using Couchbase in a web application. This time around we’re going to take a look at creating a full stack application using the Couchbase, Express.js, AngularJS, and Node.js (CEAN) stack.

You might be familiar with the MongoDB, Express.js, AngularJS, and Node.js (MEAN) stack, but I’m going to show you that this is just as possible with Couchbase and even easier to do.

We’re going to see how to install and configure all four of the CEAN stack services as well as make a very simple application out of them.

The whole setup process doesn’t take long so we’re going to run through it real quick.

Installing Couchbase Server

Let’s start with installing our database, Couchbase Server. Head over to the downloads section of the Couchbase website and get the latest installation for your computer, whether it be Mac, Windows, or Linux.

Install the file that you downloaded and launch Couchbase Server. Upon first launch it should take you through a configuration wizard. The wizard takes about two minutes to complete.

Creating A Bucket And Index

Couchbase stores all NoSQL documents in what’s called a bucket. We’ll need to create one called restful-sample in order to continue in the application. From the Data Buckets tab of your Couchbase administrative dashboard, choose to Create New Data Bucket and name it appropriately.

Couchbase Dashboard Create Bucket

With the bucket created, we need to create a primary index on it. This will allow us to run N1QL queries against the data. You’ll see what they are and why they are beautiful deeper in this tutorial.

Run the Couchbase Query Client (CBQ) from the Terminal or Command Prompt. On a Mac, it is found at the following path:

./Applications/Couchbase Server.app/Contents/Resources/couchbase-core/bin/cbq

On a Windows computer it is found at the following path:

C:/Program Files/Couchbase/Server/bin/cbq.exe

With CBQ open, run the following statement to create a primary index:

CREATE PRIMARY INDEX ON `restful-sample` USING GSI;

Our Couchbase Bucket is now ready to go!

Installing Node.js

Now we’re going to install Node.js. Although we won’t see it in this step, the Node Package Manager (NPM) included with Node.js will be used to download Express.js and its dependencies. Head on over to the Node.js website and download whatever version is appropriate for your operating system.

Install Node.js however you typically would based on your operating system.

Setup Your Node.js Project

Create a new directory, maybe on your desktop, called CEAN. Using the Terminal (Mac and Linux) or Command Prompt (Windows), run the following with CEAN as your current working directory:

npm init

Answer all the questions to the best of your ability, or just create a file called package.json with the following contents:

{
  "name": "cean-project",
  "version": "1.0.0",
  "description": "An example of using the Node.js SDK for Couchbase to create a simple CEAN stack",
  "author": "Couchbase, Inc.",
  "license": "MIT"
}

Since we have NPM working through the Node.js installation, we need to install Express.js and a few other dependencies locally to our project. From the Terminal or Command Prompt, run the following command:

npm install body-parser couchbase express uuid --save

Let’s break down what we just installed. The body-parser dependency will allow us to make POST requests to our backend API. The couchbase dependency will allow you to communicate from application server to Couchbase Server. The express dependency of course allows us to use the Express.js framework and the uuid dependency will allow us to generate unique ids later in our project.

Before we bring setup to a close, lets define our project structure. Go ahead and create the following files and directories:

CEAN
    routes
        routes.js
    models
        recordmodel.js
    public
        js
            app.js
        css
        templates
            item.html
            list.html
        index.html
    app.js
    package.json
    config.json

We’ll be adding other libraries and style sheets, but above is everything we’ll be creating.

The Project Back-End

The back-end will be responsible for serving data to our front-end (AngularJS). It is endpoint based making separation between front-end and back-end easier.

Configuring The Server

All our server configuration will be in the app.js file found at the root of our project. However, to keep our project clean, we’re going to put all our constant variables in their own file. This file will be called config.json and it will contain the following:

{
    "couchbase": {
        "server": "127.0.0.1:8091",
        "bucket": "restful-sample"
    }
}

We essentially only have two constants for this project. What server we plan to use and what bucket we want to use. A bucket in Couchbase terms is a collection of NoSQL documents.

Open app.js at the root of your project and include the following code:

var express = require("express");
var bodyParser = require("body-parser");
var couchbase = require("couchbase");
var path = require("path");
var config = require("./config");
var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

module.exports.bucket = (new couchbase.Cluster(config.couchbase.server)).openBucket(config.couchbase.bucket);

app.use(express.static(path.join(__dirname, "public")));

var routes = require("./routes/routes.js")(app);

var server = app.listen(3000, function () {
    console.log("Listening on port %s...", server.address().port);
});

Let’s break down what is happening here. First off we are requiring all the dependencies that we downloaded with NPM into our project. We are then telling the application server that we want to accept POST requests with JSON bodies as well as URL encoded data.

module.exports.bucket = (new couchbase.Cluster(config.couchbase.server)).openBucket(config.couchbase.bucket);

Here we connect to the Couchbase cluster and open a particular bucket, both defined in our config.json file. This opened bucket is assigned to a global variable through the module.exports command.

app.use(express.static(path.join(__dirname, "public")));

The above line will allow us to place HTML, JavaScript and other front-end files in our project. The front-end and back-end will still communicate over requests, but the front-end code is still going to be bundled with our project.

Finally we are including our routes/routes.js file which will contain paths to each of our endpoints and the server listener.

Designing Our Database Model And Functions

Before we set up our routes, let’s determine some functions for communicating with the database. All of these functions will go into the RecordModel class found in the models/recordmodel.js file. These functions will be responsible for inserting, updating, retrieving, and deleting data from Couchbase. Pretty much your CRUD basics.

Let’s start by laying the foundation to this models/recordmodel.js file. Open it and add the following:

var uuid = require("uuid");
var db = require("../app").bucket;
var config = require("../config");
var N1qlQuery = require('couchbase').N1qlQuery;

function RecordModel() { };

module.exports = RecordModel;

You can see above that we are requiring the uuid dependency that we downloaded as well as the app.js and config.json files from the root of our project. This is because we plan to use all these.

Now let’s break down all the functions starting with the save function. In your models/recordmodel.js file, add the following function before the module.exports = RecordModel; line:

RecordModel.save = function(data, callback) {
    var jsonObject = {
        firstname: data.firstname,
        lastname: data.lastname,
        email: data.email
    }
    var documentId = data.document_id ? data.document_id : uuid.v4();
    db.upsert(documentId, jsonObject, function(error, result) {
        if(error) {
            callback(error, null);
            return;
        }
        callback(null, {message: "success", data: result});
    });
}

What is going on here? Well we are accepting an object of data and a callback passed in from the soon to be created parent route. We are extracting only the data elements we need from the data variable and we are constructing a document key to be used. This save function will handle both updates and inserts (upsert) so keys are very important. Basically, if a document id was passed from the route, use it, otherwise create some unique value and use that. Upsert will decide whether or not we should insert or update.

Depending on the how the upsert function responds is how we make use of the parent callback function.

With save out of the way, lets take a look at the getByDocumentId function for fetching a specific document. In your models/recordmodel.js file, add the following:

RecordModel.getByDocumentId = function(documentId, callback) {
    var statement = "SELECT firstname, lastname, email " +
                    "FROM `" + config.couchbase.bucket + "` AS users " +
                    "WHERE META(users).id = $1";
    var query = N1qlQuery.fromString(statement);
    db.query(query, [documentId], function(error, result) {
        if(error) {
            return callback(error, null);
        }
        callback(null, result);
    });
};

This function again is accepting two parameters from the parent endpoint, a document id and a callback. However, this time we are using a very SQL-like statement referred to by Couchbase as N1QL. It is a parameterized query to prevent injection attacks, but it essentially just finds any NoSQL document where the document key matches the value passed in the function.

The next function, delete, is pretty simple. Inside the models/recordmodel.js file add the following:

RecordModel.delete = function(documentId, callback) {
    db.remove(documentId, function(error, result) {
        if(error) {
            callback(error, null);
            return;
        }
        callback(null, {message: "success", data: result});
    });
};

We’re just passing a document id and a callback and making use of the Node.js SDK for Couchbase. Very simple and nothing special.

Finally our last function, getAll, which will get all documents from our Couchbase bucket:

RecordModel.getAll = function(callback) {
    var statement = "SELECT META(users).id, firstname, lastname, email " +
                    "FROM `" + config.couchbase.bucket + "` AS users";
    var query = N1qlQuery.fromString(statement).consistency(N1qlQuery.Consistency.REQUEST_PLUS);
    db.query(query, function(error, result) {
        if(error) {
            return callback(error, null);
        }
        callback(null, result);
    });
};

Again, we’re using N1QL here because it is very convenient for getting a bunch of documents back.

Wew! We now have all the logic in our CEAN application that interfaces with Couchbase Server. This leaves us to designing our endpoints and font-end.

Creating API Route Endpoints

Everything we do here will be in the routes/routes.js file. The foundation to this file will look like the following:

var RecordModel = require("../models/recordmodel");

var appRouter = function(app) {

};

module.exports = appRouter;

Every route will end up inside of the appRouter function. Let’s go in the same order we did for the class functions, starting with the save route:

app.post("/api/save", function(req, res) {
    if(!req.body.firstname) {
        return res.status(400).send({"status": "error", "message": "A firstname is required"});
    } else if(!req.body.lastname) {
        return res.status(400).send({"status": "error", "message": "A lastname is required"});
    } else if(!req.body.email) {
        return res.status(400).send({"status": "error", "message": "A email is required"});
    }
    RecordModel.save(req.body, function(error, result) {
        if(error) {
            return res.status(400).send(error);
        }
        res.send(result);
    });
});

When the user (or front-end) hits the /api/save endpoint with a POST request, the above will happen. We first make sure the POST body contains a firstname, lastname, and email, otherwise we return an error. If those conditions are met then we call the save function of our RecordModel class. The callback is passed and depending on what the child function returns, is what we return back to the user (or front-end).

With save out of the way, let’s look at the get endpoint for getting a particular document from the database:

app.get("/api/get", function(req, res) {
    if(!req.query.document_id) {
        return res.status(400).send({"status": "error", "message": "A document id is required"});
    }
    RecordModel.getByDocumentId(req.query.document_id, function(error, result) {
        if(error) {
            return res.status(400).send(error);
        }
        res.send(result);
    });
});

When the front-end hits the /api/get endpoint with a GET request, the above will happen. We first make sure the GET query contains a document_id and if that condition is met then we can proceed to calling the getByDocumentId function of our RecordModel class.

I’m sure you can see a trend here in our endpoint design.

Moving on, we want to make an endpoint for deleting data. In the routes/routes.js file add the following function:

app.post("/api/delete", function(req, res) {
    if(!req.body.document_id) {
        return res.status(400).send({"status": "error", "message": "A document id is required"});
    }
    RecordModel.delete(req.body.document_id, function(error, result) {
        if(error) {
            return res.status(400).send(error);
        }
        res.send(result);
    });
});

If a POST request to the /api/delete endpoint is made above, we first confirm that the document_id exists in the POST body. If it does, then call the delete function of the RecordModel class.

Finally we have our last endpoint. The last endpoint is responsible for returning all documents that exist in the database. It can be seen in the following code:

app.get("/api/getAll", function(req, res) {
    RecordModel.getAll(function(error, result) {
        if(error) {
            return res.status(400).send(error);
        }
        res.send(result);
    });
});

There are no requirements above. As long as a GET request is made to the /api/getAll endpoint, data will be returned.

Our back-end is done. We can celebrate now! Not exactly, we still need a front-end to communicate to it. Any front-end will work, but in this CEAN stack we’re going to be using AngularJS.

The Project Front-End

The front-end will be responsible for requesting data from our back-end (Node.js and Couchbase Server). It makes HTTP requests to the back-end allowing for better separation and flexibility between the front-end and back-end.

Including All Third-Party Libraries And Styles

The front-end we’re making is of course using AngularJS, but it is also using the AngularJS UI-Router library as well as Twitter Bootstrap. The first thing we want to do is download everything.

After downloading everything, place the minified (.min.js) files in your project’s public/js directory. Place all CSS files in your project’s public/css directory, and place all fonts in your project’s public/fonts directory.

Now everything can be included in your projects public/index.html file. Open it and include the following:

<!DOCTYPE html>
<html>
    <head>
        <link rel="stylesheet" href="css/bootstrap.min.css">
        <script src="js/angular.min.js"></script>
        <script src="js/angular-ui-router.min.js"></script>
        <script src="js/bootstrap.min.js"></script>
        <script src="js/app.js"></script>
    </head>
    <body ng-app="recordsapp">
        <div style="padding: 20px">
            <div ui-view></div>
        </div>
    </body>
</html>

You may be wondering what ng-app="recordsapp" is or what <div ui-view></div> is. Don’t worry, that is coming soon.

Creating The AngularJS Foundation

Inside your public/js directory you should have an app.js file. Go ahead and add the following foundation code:

angular.module("recordsapp", ["ui.router"])

.config(function($stateProvider, $urlRouterProvider) {

})

.controller("MainController", function($scope, $http, $state, $stateParams) {

});

You can see here that we’ve named our module recordsapp which relates to what we saw in the public/index.html file. We’ve also injected the ui.router and built functions for our .config and .controller.

Configuring The AngularJS UI-Router

If you’re unfamiliar with the AngularJS UI-Router, it is a wonderful library for adding multiple screens (views) to your front-end application. All configuration for these views will end up in our config function in the public/js/app.js file:

.config(function($stateProvider, $urlRouterProvider) {
    $stateProvider
        .state("list", {
            "url": "/list",
            "templateUrl": "templates/list.html",
            "controller": "MainController",
            "cache": false
        })
        .state("item", {
            "url": "/item/:documentId",
            "templateUrl": "templates/item.html",
            "controller": "MainController",
            "cache": false
        });
    $urlRouterProvider.otherwise("list");
})

Essentially we’re creating two views. We are creating a list view and a view for adding or editing items of the list. Each of the routes points to a particular controller and template file. Both routes point to the MainController that we’ll see soon, but each point to either a public/templates/list.html file or public/templates/item.html file.

By default, the list view will show through the $urlRouterProvider.otherwise("list"); line.

Designing Our View Routes

We know which template files we need to work on now as explained by the AngularJS UI-Router configuration. Starting with the public/templates/list.html file, add the following code:

<button class="btn btn-primary" ui-sref="item">New Item</button>
<br />
<table class="table table-striped" ng-init="fetchAll()">
    <thead>
        <tr>
            <th>First Name</th>
            <th>Last Name</th>
            <th>Email</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="(key, value) in items">
            <td>{{value.firstname}}</td>
            <td>{{value.lastname}}</td>
            <td>{{value.email}}</td>
            <td><a href="" ui-sref="item({documentId: key})">edit</a> | <a href="" ng-click="delete(key)">delete</a></td>
        </tr>
    </tbody>
</table>

Essentially we have a table that loops through each object within a particular object to populate the table. Functions like fetchAll and delete are still unknown at this point, but they are coming. If you take a look at the ui-sref items in the code you’ll notice they are referring to particular UI-Router states. One of which passes in an optional parameter containing the document key. You’ll see how it’s used soon.

The second template we want to look at is the public/templates/item.html file:

<form>
    <div class="form-group">
        <label for="firstname">First Name</label>
        <input type="text" class="form-control" id="firstname" placeholder="First Name" ng-model="inputForm.firstname">
    </div>
    <div class="form-group">
        <label for="lastname">Last Name</label>
        <input type="text" class="form-control" id="lastname" placeholder="Last Name" ng-model="inputForm.lastname">
    </div>
    <div class="form-group">
        <label for="email">Email</label>
        <input type="text" class="form-control" id="email" placeholder="Email" ng-model="inputForm.email">
    </div>
    <button type="button" class="btn btn-danger" ui-sref="list">Cancel</button>
    <button type="button" class="btn btn-success" ng-click="save(inputForm.firstname, inputForm.lastname, inputForm.email)">Save</button>
</form>

This file is nothing more than a form. When the success button is pressed, a save function is called. We haven’t created it yet, but it is coming.

Adding Our Controller Logic

The last part of our front-end is all of the application logic to fuel the visuals. Inside your project’s public/js/app.js file, add the following to your controller:

.controller("MainController", function($scope, $http, $state, $stateParams) {

    $scope.items = {};

    $scope.fetchAll = function() {
        $http(
            {
                method: "GET",
                url: "/api/getAll"
            }
        )
        .success(function(result) {
            for(var i = 0; i < result.length; i++) {
                $scope.items[result[i].id] = result[i];
            }
        })
        .error(function(error) {
            console.log(JSON.stringify(error));
        });
    }

    if($stateParams.documentId) {
        $http(
            {
                method: "GET",
                url: "/api/get",
                params: {
                    document_id: $stateParams.documentId
                }
            }
        )
        .success(function(result) {
            $scope.inputForm = result[0];
        })
        .error(function(error) {
            console.log(JSON.stringify(error));
        });
    }

    $scope.delete = function(documentId) {
        $http(
            {
                method: "POST",
                url: "/api/delete",
                data: {
                    document_id: documentId
                }
            }
        )
        .success(function(result) {
            delete $scope.items[documentId];
        })
        .error(function(error) {
            console.log(JSON.stringify(error));
        });
    }

    $scope.save = function(firstname, lastname, email) {
        $http(
            {
                method: "POST",
                url: "/api/save",
                data: {
                    firstname: firstname,
                    lastname: lastname,
                    email: email,
                    document_id: $stateParams.documentId
                }
            }
        )
        .success(function(result) {
            $state.go("list");
        })
        .error(function(error) {
            console.log(JSON.stringify(error));
        });
    }

});

That is a lot of $http requests, and that’s the point! The front-end of this stack is only suppose to request data from the back-end. It makes it very modular.

Running The Application

At this point your Couchbase Server should be up and running. From the Command Prompt or Terminal, run the following with the project as your current working directory:

node app.js

If you visit http://localhost:3000 from your web browser you should have a fully functional CEAN stack application.

CEAN Stack Simple Application

Above is what your application should look like.

Conclusion

You just saw how to build a full stack CEAN stack application. Our code was short, clean, and very easy to understand thanks to the beauty of the Node.js SDK for Couchbase.

If you’re interested in the full source code to this project, it can be downloaded from the Couchbase Labs GitHub repository.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.