Node.js Code & Style Guide

There is no official document that governs the style of node.js applications. This guide is my opinionated attempt to bring you a good set of instructions that will allow you to create beautiful and consistent software. You are free to adapt to your own code style and practice, and stick to it.

Node.js Code Style Guide

This article is a personal adaptation from Felix's Node.js Style Guide, an excellent resource to derive inspiration from.

Code Editor

You are free to pick an editor of your choice. Popular choices are vim, eclipse, netbeans, webstorm. If you find Eclipse and Net-beans too CPU hogging, you can try Sublime Text or Cloud9 editors. Both editors offer excellent features for writing code, almost as good as a full featured IDE. You can easily install plug-ins for code formatting and code completion to suit your needs.

Indentation

Tab space should be set to 2 spaces. Convert tabs to spaces. You can configure your favorite editor to do this automatically for you.

Vim

Edit the .vimrc file to contain this:

set tabstop=2 shiftwidth=2 expandtab
Eclipse

Make sure you have downloaded Eclipse for JavaScript developers, it comes bundled with lots of js goodies. Customize your preferences to use spaces for indentation.

1. Go to: Windows > Preferences > Javascript > Editor > Typing > Tabulators
2. Click on 'formatter preference page'
3. Edit:
  - set tab policy as 'spaces only'
  - indentation size: '2'
  - tab size: '2'
4. Save this as new preferences

*Note: you have to do this for all your work spaces.
Netbeans
1. Go to Tools > Options > Editor > Formatting
2. Set 'Number of spaces per Ind..' to 2
3. Set 'Tab size' to 2.
4. Press Ok button.
Sublime Text

Install jsFormat plug-in for Sublime Text editor, you can do this easily with the package manager.

1. Go to Preferences > Package Settings > jsFormat > Settings - User
2. Edit/Update user preferences, a sample config is shared below:
  {
    "brace_style": "collapse",
    "jslint_happy": true,
    "keep_array_indentation": false,
    "max_preserve_newlines": 4,
    "preserve_newlines": true,
    "keep_function_indentation": false,
    "indent_with_tabs": false,
    "ensure_newline_at_eof_on_save": true,
    "space_before_line_starters": true
  }

Variables

Variables exist throughout the function they have been declared in, irrespective of where they were first declared. Blocks defined by braces {} do not start a new scope.

As corollary of this, always declare variables at the start of the function. The only exception is a for loop index variable.

for (var i = 0; i < arr.length; i++) {
  ...
  ...
}

Functions

Public functions should have a body of comments preceding them, that explains input/output parameters and any usage quirks. Add @api public to indicate exposed interfaces.

/**
 * Apply the values to a mail template and return the compiled mail object.
 * @param id: ID of the email template being used
 * @param tokens: list of objects to be used 
 * @param callback(err, mails)
 * @return this - for chaining
 * @api public
 */

function compileTemplate (id, tokens, callback) {
  ...
  ...
}

Default Arguments

In Node.js, functions can take variable number of arguments. It is possible to specify defaults in many ways.

One way is to check for the presence of last variable and assign it a default value if not defined.

function myFunc (x, y, z) {
  if (!z) {
    z = 12;
  }
  ...
}

If you were to expose two api's viz, sendmail (options, callback) and sendmail (callback) where second implementation does require the options object, you could do that like this;

function sendmail (options, callback) {
  if (typeof options === 'function') {
    callback = options;
    options = undefined;
  }
  ...
}

It is also possible to do this using the arguments object.

function myFunc (x, y, z) {
  if (arguments.length === 1) {
    // only x is available
    ...
  } else if (arguments.length === 2) {
    // x and y are available
    ...
  }
  ...
}

Note that for historical reasons arguments is not an array, even though it looks like one. It is easy to convert it to an array though;

var args = Array.prototype.slice.call(arguments);

Argument Types

Sometimes functions would accept arguments of different types, say a function lookup (entry, callback) could accept single entry or multiple entries as arguments.

function lookup (entry, callback) {
  if (entry instanceof String)
    entry = [ entry ];

  // Now write code assuming entry is an array
  ...
}

Objects

To create a new class, define a constructor as follows;

var MyObject = function () {
};

Constructor names should always begin with uppercase. This helps differentiate constructors from regular functions.

When creating a new object of type MyObject, use the new keyword.

var obj = new MyObject();
assert(obj instanceof MyObject);

One common error is to omit the new keyword when calling the constructor. This can lead to unexpected results, as this would then have different values. Naming the constructor in uppercase is one way of addressing this. Another is to include the following check in the constructor.

var MyObject = function () {
  if (!(this instanceof MyObject)) {
    throw new Error ('MyObject constructor called without new');
  }
  ...
};

Inheritance

Using util.inherits is the preferred way for making one object inherit another. To inherit an object, call the parent constructor, and then call util.inherits.

// To inherit Model object into States object

var util = require('util');
var Model = require('./model');
function States () {
  Model.call(this, 'states');
}
util.inherits(States, Model);

Errors

Errors in node should be a subclass of the Error class. To throw a new error, create it first using new.

var err = new Error();

Additionally, you can pass an error message while constructing the error object.

var err = new Error('User does not exist');

This error can subsequently be accessed as err.message.

Strict Mode & Namespace

Node 0.6+ supports strict mode, which prevents using undeclared variables and can be a lifesaver. In each file, let this be the first statement;

'use strict';

On the client side - not all browser will support strict mode, so it is best to limit yourself to the function form, which ensures your use strict keyword only applies to your function.

(function() {
  // your variables and code goes here
})();

Variables exist in the function they are defined in, so using the above prevents namespace pollution and your variables would not be leaked out.

Another way to prevent namespace pollution is by declaring everything inside of another variable, as its properties.

var myModule = {};

myModule.someVar = 'some value';
myModule.someFunc = function () {
  ...
}

Now there won't be a chance of someVar interfering with another someVar declared elsewhere.

Please note that everything in a module that is required into another file is private by default, so the above doesn't apply there. Unless you use module.exports or exports to export it, module variables will not be accessible outside.

Modules

Requiring a Module

Declare each variable on a separate line

var util = require('util');
var mysql = require('mysql');
Writing a new Module

When you require a module in a file, its content is executed. This means that any code that is not inside a function will be executed. However, this happens only once due to the presence of require cache. You can take advantage of this feature to do one time initialization activities.

Similarly, adding global variables to the module is also safe because node will automatically create a function scope.

It's a good practice to add some minimal test code at the bottom of the module file. This helps in stand-alone testing each individual module.

/************************ Test Code ************************/
if (require.main === module) {
  (function () {
    // your test code goes here
    ...
  })();
}

Organize your code - put related modules in a sub-directory, and then expose them via an index.js. Re-factor your code if it gets too lengthy, as good practice, ensure each of your module conclude in about 200 lines of code. A small amount of time spent in this part of the code will probably save you from a lot of trouble later.

exports

Any interfaces you want to expose should be added to the exports object. Generally, we could just overwrite exports (or module.exports - both are equivalent), like this;

module.exports = {
  // code goes here
};

or better still,

var myModule = {
  get: function () {
    ...
  },

  set: function (x) {
    ...
  }
};

module.exports = myModule

Never declare a global variable inside module.exports, unless you want to make something really global. Everything outside module.exports is private, and everything specified inside module.exports is public. The following is very dangerous practice;

module.exports = function () {
  var i = 5;
  // do something
};

Now i wound not only be defined everywhere, it would be set to 5. This is going to break stuff somewhere, and must be avoided.

Loops

When looping over an array, JavaScript supports multiple forms.

C Style for Loop
var i = 0;
for (i; i < items.length; i++) {
  var item = items[i];
  // do something with item
}
forEach Loop
items.forEach (item, index, arr) {
  // do something with item
}
Iterating over keys
var entry;
for (entry in items) {
  var item = items[entry];
}

Using the form (C Style) is preferred because it is several times faster than other examples. Use the last form (iterating over keys) only when iterating over an Object.

Callbacks

When to use callbacks

Use callbacks if you really have to use one. If your function is not blocking in nature, i.e., if no IO operations (file reading/network IO/db query) are involved and no other callback is being called from that function, then it should not be written as a callback. For example, a function that validates its arguments or re-formats the arguments in some way is not expected to block and shouldn't be written to take a callback argument.

Following piece of code is bad practice;

function validate (args, callback) {
  if (args && args.length > 4) {
    callback (null, args);
  } else {
    callback(new Error('args not set'));
  }
}

You should just do this as a simple function that returns a value.

function (validate) {
  if (args && args.length > 4)
    return args;
  return null;
}

By convention, every callback has error as its first argument. You may notice in some of the core node.js api methods, this is not followed strictly, but that is only to conform to some historical code convention. Ensure to have the error object as the first argument in callbacks that you write. Within a callback, it is preferred to handle the success case first.

module.getUserDetails(filters, function (err, res) {
  if (!err && res) {
    // handle success case
    ...
  } else {
    // the failure case
    ...
  }
});

Avoid using return in a callback, it makes debugging difficult.

Error Handling in Callbacks

Do not use try/catch in a callback. Event loops would make it useless. The only place where you should use try/catch is when using JSON.parse.

As a corollary to this, do not throw errors using throw. The right way to throw errors is to call the next callback with an error argument.

By convention, a callback function always has an error object as the first argument. This is undefined if there are no errors.

function mycallback (err, data) {
  if (!err) {
    // do something
    ...
  } else {
    // deal with the error
    ...
  }
}

Do not create closures if it is not required. This is bad practice;

function mycallback (id, callback) {
  var query = 'select * from register where id=' + id;
  db.query (query, function (err, res) {
    if (err)
      callback(err, null);
    else
      callback(null, res);
  });
}

Instead of this, just write the above code as;

function mycallback (id, callback) {
  var query = 'select * from register where id=' + id;
  db.query (query, callback);
}

Closures and Loop Callbacks

Consider the following piece of code;

for (var i = 0; i < idlist.length; i++) {
  var item = idlist[i];
  db.query ('select * from register where id=' + id, function(err, res) {
    console.log('query executed for id: ' + id);
  });
}

This is not going to work as expected. Here, variable id is accessible in the callback because a closure is created. The closure has access to the variable, but it gets the value of i at the time it is executed. It is quite likely that the above loop will have all callbacks print the same, final value of id (idlist[idlist.length - 1]). This is because by the time the query finishes, the loop will have completed and the value of id has changed to this.

There are several ways to fix this. But first, other than the fact that the above code doesn't work, please remember that closures have a cost, and we should avoid creating closures in a loop. If we didn't require to use the variable id here, we should have written it like this to avoid closure being created;

function postQuery (err, res) {
  // do something with the results of the query
}

for (var i = 0; i < idlist.length; i++) {
  db.query('select * from register where id=' + idlist[i], postQuery);
}

The code above will not create closures. i, if used inside postQuery will be undefined.

So how do we access i in the loop as intended earlier, there are several ways to do so.

  • Create a scope, and pass i as an argument.
    for (var i = 0; i < idlist.length; i++) {
    var id = idlist[i];
    (function () {
      db.query('select * from register where id=' + id, function (err, res) {
        console.log('query executed for id: ' + id);
      });
    })(id);
    }
    

Variables have function scope in JavaScript. Passing id as an argument means that id that is accessible to db.query is a different variable from the id in the loop.

  • Use a variable that is not modified in loop iterations;
    for (var i = 0; i < idlist.length; i++) {
    var id = idlist[i];
    db.query('select * from register where id=' + id, function (err, res) {
      // use idlist here directly, instead of using id.
    });
    }
    
Waiting for the last callback

Sometimes you want to wait for the the last callback to execute and then proceed to do something further. The technique here is to use a counter and set it to the number of loop iterations. Each callback should decrement the counter by 1, and then check if the result is zero. The last counter will always get a zero result and can trigger the post processing.

var results = {};
var flag = idlist.length;

function check () {
  if (--flag === 0) {
    callback(null, results);
  }
}

for (var i = 0; i < idlist.length; i++) {
  var id = idlist[i];
  (function () {
    db.query('select * from register where id=' + id, function (err, res) {
      results[id] = res;
      check();
    });
  })(id);
}
Assigning Default Values

Sometimes, a default value can simply be assigned with a logical or operator.

var limit = req.query.limit || 30;

This is more concise and readable than using an if statement.

Event Emitter

Node.js is asynchronous, and there are two ways in node to return results back to the caller.

  • via callbacks
  • via event emitters

Event emitters are more advanced features than callbacks, and a very powerful programming paradigm in node.js. They are more like the event loop that runs on the inside.

server.on('connection', function (stream) {
  console.log('someone connected!');
});

Logging

Use util.log to log output. Never use console.log. In case of errors, use util.error.

I have explained working with git on the other article Effective Git Workflow, which discusses best practices for effective git usage.

Alright then, I hope to have covered basic concepts for you to get started with node.js. Let me know if you have any specific questions or doubts.

Happy Coding!

\m/

Vinayak Mishra

Vinayak Mishra, a Cricket Enthusiast with keen interest in web and mobile applications. Hails from Mithila, Nepal, married to Rani and dad to his bundle of joy Bunu. Lives in New Delhi, India. You can follow him on Twitter or check out his website, vnykmshr.com.