Essential Node.js Development Concepts, part1

Hold on Cowboy

This blog post is pretty old. Be careful with the information you find in here. It's likely dead, dying, or wildly inaccurate.

Every project tends toward insanity, it’s your job to Keep it Sane, Stupid.

This is the first in a multipart series on Node.js concepts that I consider to be essential to writing sane web apps. You already know how to Node and now it’s time to get serious about building apps. The problem you’ll run into every growing project is complexity. These concepts will help you keep things sane.

Fat Models, Skinny Controllers 

Somewhere back in my PHP days while working with Zend Framework I read an article that talked about keeping your controllers small and put all your work / logic in your models. This rings true in every language / framework that I have encountered. Let’s see why.

// Don't do this, this is a Fat Controller
exports.controller = function(req, res, next) {

    var id = param('id');

    // Look up doc in DB
    Model.find(id, function(err, doc) {

      var data = req.body;
      var error = null;

      // validate input
      if (!data.name) {

        // Assign an error
        error = 'Missing name';
        return res.send(400, error);

      } else {

        // Set new data
        doc.set(data.name);

        // Save to db
        doc.save(function(err, doc) {
          if (error) {
            return next(error);
          } else {
            res.send(200, doc)
          }
        });

      }
    }
}

There is so much bad in this example, not just a Fat Controller.

Testing Controllers is hard 

Controllers are hard to test, in fact I would say near impossible to unit test. It takes an integration test to see if your controllers are operating as you expect.

To unit test this controller you’ll first going to have to stub out req, res, and next. You’re going to have a DB set up to hit because there is no way to stub out the Model object in this piece of code, so it’s going to hit the DB. We’ll now you’re going to have seed the DB with a document that matches the id we are trying to update.

See how what should be a simple unit test has erupted into a whole lot of setup?

Controllers also offer no opportunity for reuse. 

Take the above controller. What if we wanted to use this code in another controller? You’d end up replicating the code in another controller (and it’s crazy unit testing).

Rules of Thumb 

  1. All functions (Controllers especially) should not be more than a screen size. Preferably only 5-15 LOC.
  2. Controllers should have very little logic in them. They gather data (from input), pass it off to a Model, receive a result and write that to output.

So with these new Rules of Thumb to code by, how do we redo this example.

// Do this, this is a Skinny Controller
exports.controller = function(req, res, next) {

    var id = param('id');

    // Look up doc in DB, let the model validate and handle errors
    Model.updateName(id, req.body, function(err, doc) {
      if (err) return next(err); // Early return, an awesome design pattern.

      // If we've made it here, it's all good
      res.send(200, doc);

    });

}

In this example, the Model layer is doing all the work of validating, error checking etc. This function is so simple it doesn’t warrant a unit test.

So we’ve seen how to put a Fat Controller on a crash diet to become a Skinny Controller. Stay tuned for the next part in the series as we go over the 12 Factor App rules in our Node.js apps.

Did this help you out? It took me a few days to piece together all this information together, I hope this saves you some time (who knows, maybe the future me will be thankful I wrote this down). Let me know your thoughts. shanestillwell@gmail.com