We’re really interested in seeing the cool projects the community is building with Orchestrate. During a brainstorming session, we talked about a Hacker News style voting app, where people can submit projects and then other people can up vote them. We authenticate people through GitHub, so we can ensure they only vote once for each project. Since we eat our own dog food, we wanted to build this off Orchestrate.

We can take advantage of Orchestrate’s events API to handle voting and ensure each user can only vote once per project, graph search so we can see what projects our friends voted on, and full text search so we can find projects. After talking about it, we decided we could throw it together pretty fast, and it’d be a great tutorial on building a real world app with Orchestrate.

You can see the finished app here or view the source on GitHub.

The tech we’ll be using

  • Built with Node.js, Express, and Passport.
  • Orchestrate Key/Value for storing user data and project data.
  • Events API for storing the votes for each project.
  • Graph for associating a project with a user.
  • Search for full text searching of projects.

Getting Started

First, we need to set up our basic app. We need to install Orchestrate, Express, Passport and the GitHub strategy for Passport.

$ npm install orchestrate express passport passport-github --save

Now we can create the basic Express app in a new index.js file.

var express = require('express'),
    passport = require('passport'),
    GitHubStrategy = require('passport-github').Strategy;

var app = express();

// configure Express
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({ secret: 'cookie monster' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);

app.get('/', function(req, res){
    res.send('hello world');
});

app.listen(process.env.PORT || 5000);

This gives us the basic app that responses with “hello world”. We have Express and Passport set up and next we’ll handle the GitHub authentication.

Authenticate with GitHub

To use GitHub for authentication we need to go to GitHub and create a new application.

Once created, we can plug the keys into our code. You can put the keys directly in your code but if you plan to use source control, it’s best not to commit those. So let’s add them to a new .env file that Heroku will use.

Create a new .env file and add your keys into these spots. We’ll also set the callback url. This is the url that GitHub redirects to after the login.

GITHUB_CLIENT_ID=XXXXXX
GITHUB_CLIENT_SECRET=XXXXXX
GITHUB_CALLBACK=http://127.0.0.1:5000/auth/github/callback

I won’t go into detail here about how the GitHub auth works, but you can read more about it on the Passport site.

var express = require('express'),
    passport = require('passport'),
    GitHubStrategy = require('passport-github').Strategy;

var app = express();

passport.serializeUser(function(user, done) {
    done(null, user);
});

passport.deserializeUser(function(obj, done) {
    done(null, obj);
});

passport.use(new GitHubStrategy(
    {
        clientID: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        callbackURL: process.env.GITHUB_CALLBACK
    },
    function(accessToken, refreshToken, profile, done) {
        // on successful auth
    }
));

// configure Express
app.use(express.logger());
app.use(express.cookieParser());
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.session({ secret: 'cookie monster' }));
app.use(passport.initialize());
app.use(passport.session());
app.use(app.router);

app.get('/', function(req, res){
    res.send('hello world');
});

app.get('/auth/github', passport.authenticate('github'));

app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/' }), function(req, res) {
    // Successful authentication, redirect home.
    res.redirect('/');
});

app.listen(process.env.PORT || 5000);

This configures Passport to use the GitHub strategy and sets up our callback paths.

Testing

Now we can start our server with foreman start visit http://127.0.0.1:5000/auth/github and we’ll be redirected to GitHub to authenticate. Upon success, GitHub will redirect back to our app. (If you’re not using the Heroku CLI, then you can put the keys into your code and run it simply with node index.js.)

Storing Data with Orchestrate

Now we get to start storing our data in Orchestrate. GitHub will return user data for the authenticated user and we want to save it. Luckily this is really easy!

Create a New Orchestrate App

In your Orchestrate Dashboard click the “New Application” button.

Create a Users Collection

Once you’ve created the application, click the “New Collection” button. This is similar to a table in a relational DB. Name it “Users”.

Get our API Key

Now we’re ready to start adding Orchestrate into our app. We first need to add our API key into our .env file (or into the code, if that’s your thing).

ORCHESTRATE_API_KEY=XXXXXXXXXX

Organizing

In the interest of organization, we’ll separate our Express routes from our app functions. We can store all our functions in a new module called functions.js

var db = require('orchestrate')(process.env.ORCHESTRATE_API_KEY);

exports.authUser = function (profile) {
}

Include the new module in our index.js file:

var votes = require('./functions.js');

Then add our authUser call into our authentication callback. When the authentication is successful, we’ll call our authUser function to save the user data.

passport.use(new GitHubStrategy(
    {
        clientID: process.env.GITHUB_CLIENT_ID,
        clientSecret: process.env.GITHUB_CLIENT_SECRET,
        callbackURL: process.env.GITHUB_CALLBACK
    },
    function(accessToken, refreshToken, profile, done) {
        // call our db to create or auth the user
        votes.authUser(profile);
    }
));

Now let’s save the user data into our User collection. We’ll create a new key for the record based on the provider (GitHub) and the username. Each piece of data needs to have a unique key. We know that the username will be unique across GitHub, but we’ll add the provider (GitHub) to it incase we want to support signing in from other services too.

exports.authUser = function (profile) {
    // use the provider and the user (incase we support other providers down the road)
    var key = profile.provider + '-' + profile.username;

    // the user data we want to store.
    var user = {
        provider:    profile.provider,
        id:          profile.id,
        displayName: profile.displayName,
        username:    profile.username,
        profileUrl:  profile.profileUrl,
        emails:      profile.emails,
        avatar:      profile._json.avatar_url,
        githubData:  profile._json
    };

    // put the user into our Collection
    db.put('users', key, user);
}

Check that it worked

In the Orchestrate Dashboard, we can do a GET call on our Users collection and see that the data is saved!

orchestrate-check-it-worked

Conditional PUT

Now each time the user signs in, the data will be saved. But we only want to save the data the first time they sign in so we don’t write the user data for existing users each time they sign in. Luckily, that’s really easy to do. We can use Orchestrate’s conditional PUT functionality. Conditional PUT works two ways, if-match will only write data if they key and ref matches, or if-none-match will only save the data if the key doesn’t exist. That’s the one we want to use. Our conditional PUT looks at the key and if there is a record at that key already, it will skip the operation. This way, the first time they sign in, we save the data, and subsequent times we don’t.

All we need to do is pass false as the 4th parameter for the PUT operation.

db.put('users', key, user, false)

Coming up next

Now we have our basic app set up. Users can sign in through GitHub and we will save the user’s data into our database.

In the next post, we’ll create projects and use Graph to associate them to users. We’ll also start collecting votes using Events.

You can see the finished app here or view the source on GitHub.

Photo Credit: KCIvey