7 min read

NodeJS has become the most anticipated web development technology since Ruby on Rails. This is not an introduction to Node. First, you must realize that NodeJS is not a direct competitor to Rails or Django. Instead, Node is a collection of libraries that allow JavaScript to run on the v8 runtime. Node powers many tools, and some of the tools have nothing to do with a scaling web application. For instance, GitHub’s Atom editor is built on top of Node. Its web application frameworks, like Express, are the competitors. This article can apply to all environments using Node. Second, Node is designed under the asynchronous ideology. Not all of the operations in Node are asynchronous. Many libraries offer synchronous and asynchronous options. A Node developer must decipher the best operation for his or her needs. Third, you should have a solid understanding of the concept of a callback in Node.

Over the course of two weeks, a team attempted to refactor a Rails app to be an Express application. We loved the concepts behind Node, and we truly believed that all we needed was a barebones framework. We transferred our controller logic over to Express routes in a weekend. As a beginning team, I will analyze some of the pitfalls that we came across. Hopefully, this will help you identify strategies to tackle Node with your team.

First, attempt to structure callbacks and avoid anonymous functions. As we added more and more logic, we added more and more callbacks. Everything was beautifully asynchronous, and our code would successfully run. However, we soon found ourselves debugging an anonymous function nested inside of other anonymous functions. In other words, the codebase was incredibly difficult to follow. Anyone starting out with Node could potentially notice the novice “spaghetti code.”

Here’s a simple example of nested callbacks:

router.put('/:id', function(req, res) {
    console.log("attempt to update bathroom");
    models.User.find({
        where: {id: req.param('id')}
        }).success(function (user) {
           var raw_cell = req.param('cell') ? req.param('cell') : user.cell;
           var raw_email = req.param('email') ? req.param('email') : user.email;
           var raw_username = req.param('username') ? req.param('username') : user.username;
           var raw_digest = req.param('digest') ? req.param('digest') : user.digest;
           user.cell = raw_cell;
           user.email = raw_email;
           user.username = raw_username;
           user.digest = raw_digest;
           user.updated_on = new Date();
           user.save().success(function () {
               res.json(user);
               }).error(function () {
                        res.json({"status": "error"});
            });
       })
   .error(function() {
          res.json({"status": "error"});
   })
});

Notice that there are many success and error callbacks. Locating a specific callback is not difficult if the whitespace is perfect or the developer can count closing brackets back up to the destination. However, this is pretty nasty to any newcomer. And this illegibility will only increase as the application becomes more complex. A developer may get this response:

{"status": "error"}

Where did this response come from? Did the ORM fail to update the object? Did it fail to find the object in the first place? A developer could add descriptions to the json in the chained error callbacks, but there has to be a better way. Let’s extract some of the callbacks into separate methods:

router.put('/:id', function(req, res) {
   var id = req.param('id');
   var query = {
       where: {id: id}
   };
    // search for user
    models.User.find(query).success(function (user) {
           
           // parse req parameters
           var raw_cell = req.param('cell') ? req.param('cell') : user.cell;
           var raw_email = req.param('email') ? req.param('email') : user.email;
           var raw_username = req.param('username') ? req.param('username') : user.username;
                                    
           // set user attributes
           user.cell = raw_cell;
           user.email = raw_email;
           user.username = raw_username;
           user.updated_on = new Date();
                                    
           // attempt to save user
           user.save()
               .success(SuccessHandler.userSaved(res, user))
               .error(ErrorHandler.userNotSaved(res, id));
       })
   .error(ErrorHandler.userNotFound(res, id))
});

var ErrorHandler = {
    userNotFound: function(res, user_id) {
        res.json({"status": "error",
                 "description": "The user with the specified id could not be found.",
                 "user_id": user_id});
    },
    userNotSaved: function(res, user_id) {
        res.json({"status": "error",
                 "description": "The update to the user with the specified id could not be completed.",
                 "user_id": user_id});
    }
};

var SuccessHandler = {
    userSaved: function(res, user) {
        res.json(user);
    }
}

This seemed to help clean up our minimal sample. There is now only one anonymous function. The code seems to be a lot more readable and independent. However, our code is still cluttered by chaining success and error callbacks. One could make these global mutable variables, or, perhaps we can consider another approach. Futures, also known as promises, are becoming more prominent. Twitter has adopted them in Scala. It is definitely something to consider.

Next, do what makes your team comfortable and productive. At the same time, do not compromise the integrity of the project. There are numerous posts that encourage certain styles over others. There are also extensive posts on the subject of CoffeeScript. If you aren’t aware, CoffeeScript is a language with some added syntactic flavor that compiles to JavaScript. Our team was primarily ruby developers, and it definitely appealed to us. When we migrated some of the project over to CoffeeScript, we found that our code was a lot shorter and appeared more legible. GitHub uses CoffeeScript for the Atom text editor to this day, and the Rails community has openly embraced it. The majority of node module documentation will use JavaScript, so CoffeeScript developers will have to become acquainted with translation. There are some problems with CoffeeScript being ES6 ready, and there are some modules that are clearly not meant to be utilized in CoffeeScript. CoffeeScript is an open source project, but it has appears to have a good backbone and a stable community. If your developers are more comfortable with it, utilize it.

When it comes to open source projects, everyone tends to trust them. In the purest form, open source projects are absolutely beautiful. They make the lives of all of the developers better. Nobody has to re-implement the wheel unless they choose. Obviously, both Node and CoffeeScript are open source. However, the community is very new, and it is dangerous to assume that any package you find on NPM is stable. For us, the problem occurred when we searched for an ORM. We truly missed ActiveRecord, and we assumed that other projects would work similarly.  We tried several solutions, and none of them interacted the way we wanted. Besides expressing our entire schema in a JavaScript format, we found relations to be a bit of a hack. Settling on one, we ran our server. And our database cleared out. That’s fine in development, but we struggled to find a way to get it into production. We needed more documentation. Also, the module was not designed with CoffeeScript in mind. We practically needed to revert to JavaScript. In contrast, the Node community has openly embraced some NoSQL databases, such as MongoDB. They are definitely worth considering.   Either way, make sure that your team’s dependencies are very well documented. There should be a written documentation for each exposed object, function, etc.

To sum everything up, this article comes down to two fundamental things learned in any computer science class: write modular code and document everything. Do your research on Node and find a style that is legible for your team and any newcomers. A NodeJS project can only be maintained if developers utilizing the framework recognize the importance of the project in the future. If your code is messy now, it will only become messier. If you cannot find necessary information in a module’s documentation, you probably will miss other information when there is a problem in production. Don’t take shortcuts. A node application can only be as good as its developers and dependencies.

About the Author

Benjamin Reed began Computer Science classes at a nearby university in Nashville during his sophomore year in high school. Since then, he has become an advocate for open source. He is now pursing degrees in Computer Science and Mathematics fulltime. The Ruby community has intrigued him, and he openly expresses support for the Rails framework. When asked, he believes that studying Rails has led him to some of the best practices and, ultimately, has made him a better programmer. iOS development is one of his hobbies, and he enjoys scouting out new projects on GitHub. On GitHub, he’s appropriately named @codeblooded. On Twitter, he’s @benreedDev.

LEAVE A REPLY

Please enter your comment!
Please enter your name here