How Piggybank uses Node, Geddy Model, and Restify

piggybank and node.js

Piggybank was started by Adam and I (hi, this is Oscar). We’re both front-end devs and JavaScript was our strong point, so when we went out looking for backend tools and frameworks we started with Node. Node has been proven by other financial companies such as Paypal so we decided to use it for our backend. It’s been working great here so I wanted to share what our setup looks like.

We won’t go into the financial integration pieces because, well, security. However, we’ll go into how we talk between our frontend service which we call Teacup, and our backend service which we call Hog (Yes, all our services are based on pig names) which talks to MySQL.

The Server (Restify)

The first step we needed to do was get a server to accept REST calls from Teacup. We looked at Express, Geddy, and Sails, but all of these were based around the idea of views. They all can work without views, but it wasn’t a priority of theirs. Piggybank is a one page app and all views are on the client, so it brought a lot of extra code and cognitive overhead we didn’t need.

That’s when I found Restify. Restify describes itself as:

“…a node.js module built specifically to enable you to build correct REST web services. It intentionally borrows heavily from express as that is more or less the de facto API for writing web applications on top of node.js.”

If you’re used to Express it’s pretty easy to understand Restify. In fact, you might not even know it’s Restify at first glance. Here’s some example code:

server.get('/users/:id/avatar/:size', function (req, res, next) {
  UsersController.getAvatar(req, res, function (response) {
    res.send(response.code, response.data);
    next();
  });
});

Here I’m setting up our GET avatar route. Just like Express, it has get(), put(), and post() methods that take a string for a route. The callback has request, response, and next params. Inside you see our UserController which has the app code to actually fetch the avatar which we’ll dig into more in a bit. Inside of that is another Express-like method: send(code, data). That sends an HTTP status code as well as the data to send back in the response to the client.

How do we get the data though? We use Geddy Model to fetch it from MySQL using vanilla Node and JavaScript for our controllers. Geddy Model is the model component Geddy the framework uses. This is what’s awesome about building frameworks from a bunch of small modules. You can pick and choose only the pieces you need from the framework. In this case we just took their model layer.

Geddy Model (Models and Controllers)

Using the UsersController.getAvatar() method we can continue digging in deeper. Our controllers are plain old Node and JavaScript. No special libraries or anything. The code looks like this:

getAvatar: function (req, res, callback) {
  var baseUrl = 'http://res.getpiggybank.com/our/cdn/url;
  var id = req.params.id;
  var size = req.params.size;
  AvatarModel.first({ userId: id }, function (err, avatarModel) {
    if (err || !avatarModel) // code to handle error
    var avatarUrl = baseUrl + '/' + size + '/' + id + '.jpg';
    var avatarImage = request(avatarUrl);
    avatarImage.on('response', function(response) {
      if (response.statusCode !== 200) {
        // Handle a default image
      }
      else {
        avatarImage.pipe(res);
      }
    });
  });
}

I’ve highlighted the Geddy Model piece and removed some misc. code and error handling. When Teacup requests an avatar it hits the GET avatar route which calls this controller method. This controller method then calls our AvatarModel and searches for the first result that matches the ID. The actual model code is pretty straightforward:

var model = require('model');

var Avatar = function () {
  this.defineProperties(
    userId: { type: 'string', required: true },
    disabled: { type: 'boolean', required: false },
    version: { type: 'string', required: true }
  });
  this.belongsTo('User');
};

Avatar = model.register('Avatar', Avatar);
module.exports = Avatar;

Here you can see us defining our data model with Geddy Model. We are also setting up associations. Avatars belong to Users. This allows us to do things like UserModel.getAvatar() automatically without writing the method ourselves. This is very similar to other ORMs.

Saving or updating is equally as easy now.

_createOrUpdateAvatar: function (id, version, callback) {
  var avatarData = {
    userId: id,
    version: version
  };
  AvatarModel.first({ userId: id }, function (err, avatarModel) {
    if (err) return callback(createResponse.error());
    if (!avatarModel) {
      avatarModel = AvatarModel.create(avatarData);
    }
    else {
      avatarModel.updateProperties(avatarData);
    }
    avatarModel.save(function (err, avatarModel) {
      if (err) return callback(createResponse.error(...));
      callback({ code: 200, data: {...} });
    });
  });
}

Again I’ve highlighted the relevant Geddy Model code. We created a private method that updates or creates an avatar entry.

First thing we do is check if the AvatarModel exists already like before. If no model exists we create it with the data provided which is the user ID and version (the version is for cache busting on our CDN, if you’re curious).

If an AvatarModel already exists we simply update it. We pass the same props to update. The create and updateProperties methods on AvatarModel are from Geddy Model and do not update the database. This allows you to monkey with stuff until it’s perfect saving you SQL calls.

Now that we’ve either created or updated the avatar we finally save it. The save method is what finally hits the DB. If it’s successful Geddy Model will return the updated model. If it failed the error response will trigger.

Security

The final piece I’d like to touch on is how we communicate between services. This part was hard for me to wrap my head around initially being a front-end person. There’s a few levels of security.

First off, most requests require a user session that is stored on Teacup once you login and then sent to other backend services for each request. About 9/10 requests require this. The ones that don’t have rate limiting applied to them so you can’t hammer our servers.

The second piece is server side key, secret and URL for each service. These are all environment variables. This means they’re not stored in our code at all, but where we host our services. Not only would you need to get into our code base, you’d need to find our environment variables before you could make any requests to the service.

Then, lastly, we host our databases elsewhere and each service has it’s own database credentials. The login info for those is also in environment variables so you would need to locate those as well in a different place for each service.

Conclusion

If there was anything that wasn’t touched on in detail that you’re more interested in, or have suggestions or concerns feel free to email me at oscar@getpiggybank.com.

Some helpful links with the tools we use for backend:

Some additional links: