Last month, we set out on building a Hacker News style voting app on Orchestrate, for an app contest idea we’ve been playing around with (more on that later). The voting app lets people submit projects, and then others can up vote them. We’re sharing step-by-step instructions on how to build this app, so you can follow along and get a good sense of Orchestrate in action.

The application is being built with Node.js, Express, and Passport. It takes advantage of Orchestrate’s Key/Value store for storing user and project data, the Events API for storing votes for each project, Graph for associating projects with users, and Search for full-text searching through all projects. If you aren’t familiar, see Orchestrate’s documentation for more info.

In the first part of this series, we set up our basic app structure, authenticated via GitHub, and stored the user data. In today’s installment, we will add the functionality for users to create and view projects!

Creating Projects

First up, we’ll create projects using Key/Value storage, and use Graph to associate them with a user. We could also use Key/value and put a user ID in the data and then use search, but the power of Graph really comes in for more advanced things like multiple users on a project.

In our functions.js we’ll start by adding a function for creating a project. This function will be passed a project object with the data to save, and the user object from the session.

exports.createProject = function (project, user) {  
    var deferred = Q.defer();

    return deferred.promise;
}

We need to create a new key for the project. This is the key we use to get and set data for this particular project. To make sure this is unique we can generate a random string to use.

var crypto = require('crypto');

function randomKey () {  
    return crypto.randomBytes(10).toString('hex');
}
exports.createProject = function (project, user) {  
    var deferred = Q.defer();

    // create a new key for the user
    var key = randomKey();
...

Now lets add votes, username, date created and updated data to the project data being saved.

exports.createProject = function (project, user) {  
    var deferred = Q.defer();

    // create a new key for the user
    var key = randomKey();

    // add the votes as 0 into the data
    project.votes = 0;

    // add the user name to the data
    project.user = user.username;

    // save the date we created it
    project.dateCreated = new Date().toISOString();
    project.dateUpdated = new Date().toISOString();
...

We save the date data in ISO 8601 format and Orchestrate will automatically index it as a date so we can search on it.

Save it to Orchestrate

exports.createProject = function (project, user) {  
    var deferred = Q.defer();

    // create a new key for the user
    var key = randomKey();

    // add the votes as 0 into the data
    project.votes = 0;

    // add the user name to the data
    project.user = user.username;

    // save the date we created it
    project.dateCreated = new Date().toISOString();
    project.dateUpdated = new Date().toISOString();

    // save the project into the DB
    db.put('projects', key, project, false)
    .then(function () {
        console.log('project created');
        deferred.resolve(project);
    })
    .fail(function () {
        console.log('new project failed');
        deferred.reject();
    });

    return deferred.promise;
}

Now, we are saving the project into Orchestrate, but we need to have it associated with the user. To do this, we’ll use Graph to create a connection from the user to the project. Then we can do a Graph search to get all project for a user.

Create Graph connection

We will add another then() statement that will create the connection. We can make the connection easily with the newGraphBuilder() function. We then tell it that we want to make a connection from the user, to the project. We can make any type of connection but for this one, we’ll call it member. We could call it owner but since down the road, we may want to support multiple users attached to a project, we’ll set the user to be a member of the project.

db.put('projects', key, project, false)  
.then(function () {
    // create a graph connection from the user to the project
    return db.newGraphBuilder()
    .create()
    .from('users', user.key)
    .related('member')
    .to('projects', key);
})

We now have a connection saying user is a member of project. One thing to note: Graph connections only go in one direction so project is not also a member of user.

Our full function is:

exports.createProject = function (project, user) {  
    var deferred = Q.defer();

    // create a new key for the user
    var key = randomKey();

    // add the votes as 0 into the data
    project.votes = 0;

    // add the user name to the data
    project.user = user.username;

    // save the date we created it
    project.dateCreated = new Date().toISOString();
    project.dateUpdated = new Date().toISOString();

    // save the project into the DB
    // this uses Orchestrate's condition PUT functionality
    db.put('projects', key, project, false)
    .then(function () {
        // create a graph connection from the user to the project
        return db.newGraphBuilder()
        .create()
        .from('users', user.key)
        .related('member')
        .to('projects', key);
    })
    .then(function () {
        console.log('project and link created');
        deferred.resolve(project);
    })
    .fail(function () {
        console.log('new project failed');
        deferred.reject();
    });

    return deferred.promise;
}

Hooking it up to Express

app.post('/myprojects/create', ensureAuthenticated,  function(req, res){  
    var project = {
        name: req.body.projectName,
        link: req.body.projectLink,
        image: req.body.projectImage,
        description: req.body.projectDesc
    };

    // create a new project for the user
    votes.createProject(project, req.user)
    .then(function () {
        req.session.success = 'Project Created!';
        res.redirect('/myprojects');
    })
    .fail(function () {
        req.session.error = 'Project failed to be created.';
        res.redirect('/myprojects');
    });
});

Get a single project

Getting a project out of the database is core functionality that we’ll use over and over. We’ll create a function that gets the project. We can then use that in our other function.

We will pass the function the project key. The function will then hit our collection and return the response.

function getProject (key) {
    return db.get('projects', key);
}

Clean up the response

The Orchestrate client returns the full http response. At this point it’s good to clean up the response and only return the data we care about. Also, Orchestrate uses Ref keys to track versions of the data. All data in Orchestrate is immutable. What this means is once data is written, it is permanent. When we update data at a key, instead of overwriting the existing data, Orchestrate creates a new data record. It automatically tracks the current version of the data and returns the newest for a get operation. The data then comes with a Ref code that identifies the version of the data. We’ll use it in the future when we start saving data. Orchestrate returns the Ref in the headers so while we are cleaning up data, let’s add that in to our response.

function getProject (key) {
    return db.get('projects', key)
    .then(function (response) {
        // get the ref out of the header
        var ref = response.headers.etag;

        return {
            ref: ref,
            data: response.body
        };
    })
    .fail(function (response) {
        console.log('get failed', response);
        return response;
    });
}

Our getProject function will now return ref (the content version ID) and data the JSON we’ve saved.

Hooking it up to Express

We can now add a route into Express so we respond to the request.

app.get('/project/:id',  function(req, res){
    votes.getProject(req.params.id)
    .then(function (response) {
        res.render('project', {
            project: response.data,
            key: req.params.id,
            user: req.user
        });
    })
    .fail(function () {
        console.log('get project error', res);
        res.render('error', { user: req.user });
    });
});

Get all projects for a user

It’s really easy to get all the projects for a given user. We’ll use the Graph search to find all projects that the user is a member of. We pass the function, the user object from our session.

exports.getMyProjects = function (user) {
    return db.newGraphReader()
    .get()
    .from('users', user.key)
    .related('member')
    .then(function (response) {
        var projects = response.body.results || [];
        return projects;
    });
}

This returns the all the projects for the user but the projects aren’t in the order we want. To do this we will sort the Array by dateUpdated. We can use the Array.sort function.

Create a new sort function

This function gets passed two objects and orders them by dateUpdated.

function sortProjectsByDate (a, b){
    var keyA = a.dateUpdated,
        keyB = b.dateUpdated;

    // Compare the 2 dates
    if(keyA < keyB) return -1;
    if(keyA > keyB) return 1;
    return 0;
}

And then adding it into our getMyProjects functions:

exports.getMyProjects = function (user) {
    return db.newGraphReader()
    .get()
    .from('users', user.key)
    .related('member')
    .then(function (response) {
        var projects = response.body.results || [];

        // sort the results
        return {
            count: response.body.count,
            projects: projects.sort(sortProjectsByDate)
        };
    });
}

Hooking it up to Express

app.get('/myprojects', ensureAuthenticated,  function(req, res){  
    votes.getMyProjects(req.user)
    .then(function (response) {
        res.render('projects', {
            projects: response.projects,
            user: req.user
        });
    })
    .fail(function () {
        console.log('get projects error', res);
        res.render('error');
    });
});

Up next: Events!

At this point in our app, we have users signing in and creating and viewing projects. In the next post we’ll get into voting for projects using the Events API.

Orchestrate gives developers access to multiple NoSQL databases through a single, RESTful API, eliminating the need to run databases in production. Learn more.