We’re a Node.js team of about 25 developers, most of them fresh out of college. Node was new to everyone. This is the style guide we put together to keep the codebase consistent.

Based on Felix’s Node.js Style Guide with our own picks where his guide left things open. Read Felix’s first – this extends it.

Formatting

2 spaces. No tabs. No discussion.

Trailing whitespace: strip it. Your editor should handle this. If it doesn’t, fix your editor.

Semicolons: always. JavaScript’s automatic semicolon insertion will bite you. Don’t rely on it.

Variables

Declare with var at the top of the function. JavaScript hoists declarations – putting them at the top makes the hoisting explicit instead of surprising.

One var per declaration:

// yes
var user = getUser(id);
var orders = [];
var i, len;

// no
var user = getUser(id),
    orders = [],
    i, len;

The comma-separated style saves lines but costs you a missing-comma bug eventually. Not worth it.

Functions

Small. Single concern. If you need “and” to describe what a function does, it’s two functions.

Name your functions. Anonymous callbacks produce useless stack traces:

// stack trace says "anonymous"
users.forEach(function(user) {
  processUser(user);
});

// stack trace says "processEachUser"
users.forEach(function processEachUser(user) {
  processUser(user);
});

When something breaks under load, you’ll want the name.

Error-first callbacks

Every callback takes err as the first argument. Every callback checks it first. This is the Node.js contract.

function getUser(id, callback) {
  db.query('SELECT * FROM users WHERE id = ?', [id], function(err, rows) {
    if (err) return callback(err);
    if (rows.length === 0) return callback(new Error('user not found'));
    callback(null, rows[0]);
  });
}

Never ignore err. Never put the happy path before the error check.

Early return

Don’t nest when you can bail out.

// no -- logic buried under three guards
function processOrder(order, callback) {
  if (order) {
    if (order.isValid) {
      if (order.hasItems) {
        submitOrder(order, callback);
      } else {
        callback(new Error('no items'));
      }
    } else {
      callback(new Error('invalid order'));
    }
  } else {
    callback(new Error('no order'));
  }
}

// yes -- guards at the top, logic at the bottom
function processOrder(order, callback) {
  if (!order) return callback(new Error('no order'));
  if (!order.isValid) return callback(new Error('invalid order'));
  if (!order.hasItems) return callback(new Error('no items'));

  submitOrder(order, callback);
}

The function reads top to bottom without branching.

Callback alignment

This is the one that trips up new developers.

When you chain async operations, the callbacks nest. The code drifts right. At three levels deep, it’s hard to follow. At five, it’s unreadable.

// drifting right
function createOrder(userId, items, callback) {
  getUser(userId, function(err, user) {
    if (err) return callback(err);
    validateItems(items, function(err, validItems) {
      if (err) return callback(err);
      saveOrder(user, validItems, function(err, order) {
        if (err) return callback(err);
        callback(null, order);
      });
    });
  });
}

The fix: break each step into a named function. Declare shared state in the outer scope.

// aligned
function createOrder(userId, items, callback) {
  var user, validItems;

  getUser(userId, onUser);

  function onUser(err, result) {
    if (err) return callback(err);
    user = result;
    validateItems(items, onValidation);
  }

  function onValidation(err, result) {
    if (err) return callback(err);
    validItems = result;
    saveOrder(user, validItems, onSaved);
  }

  function onSaved(err, order) {
    if (err) return callback(err);
    callback(null, order);
  }
}

Each step is a named function. The flow reads top to bottom. Nesting stays flat. The inner functions close over the outer scope, so user and validItems are accessible where needed.

This pattern takes getting used to. Once it clicks, you don’t go back to nesting.

Modules

One export per file for constructors. Named exports for utilities:

// user-service.js
module.exports = UserService;

// string-utils.js
module.exports.capitalize = capitalize;
module.exports.slugify = slugify;

Keep require calls at the top of the file. Core modules first, then npm packages, then local modules:

var http = require('http');
var path = require('path');

var express = require('express');
var async = require('async');

var UserService = require('./services/user-service');
var config = require('./config');