This is a guide for writing consistent and aesthetically pleasing node.js code. It is inspired by what is popular within the community, and flavored with some personal opinions.
This guide was created by Felix Geisendörfer and is licensed under the CC BY-SA 3.0 license. You are encouraged to fork this repository and make adjustments according to your preferences.
This has been modified for use by LN eCommerce teams.
Use 2 spaces for indenting your code and swear an oath to never mix tabs and spaces - a special kind of hell is awaiting you otherwise.
Just like you brush your teeth after every meal, you clean up any trailing whitespace in your JS files before committing. Otherwise the rotten smell of careless neglect will eventually drive away contributors and/or co-workers.
According to scientific research, the usage of semicolons is a core values of our community. Consider the points of the opposition, but be a traditionalist when it comes to abusing error correction mechanisms for cheap syntactic pleasures.
Soft-limit your node.js code to 100 characters per line, with a hard limit of 120 characters. Jade template files may be 120 characters per line.
Some may prefer 80-column lines, but with the emergence of wide-screen monitors and disuse of printed page, the effort involved to foist that on new hires is not worth it. In fact, it tends to make code less easy to digest, because it makes the code take up more lines and vertical space. So we enforce 100-column lines instead for code.
That said, you should make every effort to avoid signficant levels of indentation.
Use single quotes, unless you are writing JSON.
Right:
var foo = 'bar';Wrong:
var foo = "bar";Your opening braces go on the same line as the statement.
Right:
if (true) {
console.log('winning');
}Wrong:
if (true)
{
console.log('losing');
}Also, notice the use of whitespace before and after the if condition.
Right:
if (true) {
console.log('winning');
} else {
console.log('bi-winning');
}Wrong:
if (true) {
console.log('losing');
}
else {
console.log('bi-losing');
}It is a standard of ours to use lodash library _.each statement instead of writing for statements. _.each
gives you the value of each array element, or the value and key of each object. It avoids having to use var to
declare loop variables, and syntax warnings about having the same loop variable in two different for statements
within single code block.
This also provides protection from the unexpected side effects of hoisting, which effects both if and
for statements.
Be sure to name your value and key (if applicable) parameters meaningfully (don't use names like 'key' and 'value').
for statements may be used to iterate through characters in a string or Buffer, if you are writing some low-level
utility function around parsing that must be fast, but this is discouraged.
Right:
var _ = require('lodash');
var fruits = ['apple', 'banana', 'cranberry'];
_.each(fruits, function(inFruit) {
console.log('saw fruit: ' + inFruit);
});
var veggies = {
'bell pepper': 'green',
onion: 'yellow'
};
_.each(veggies, function(color, veggie) {
console.log('saw veggie: ' + veggie + ' with color: ' + color);
});Wrong:
var fruits = ['apple', 'banana', 'cranberry'];
for (var i = 0; i < fruits.length; i++) {
console.log('saw fruit: ' + fruits[i]);
}
var veggies = {
'bell pepper': 'green',
onion: 'yellow'
};
for (var veggie in veggies) {
var color = veggies[veggie];
console.log('saw veggie: ' + veggie + ' with color: ' + color);
}The lodash library _.contains function must be used if you want to check that an element is present in an Array. You
must not use Array.indexOf(), since it is easy to mistake the meaning and invert the logic.
Right:
var fruits = ['apple', 'banana', 'cranberry'];
if (_.contains(fruits, 'banana')) {
console.log('winning');
}Wrong:
var fruits = ['apple', 'banana', 'cranberry'];
if (fruits.indexOf('banana') !== -1) {
console.log('losing');
}Declare one variable per var statement, it makes it easier to re-order the lines. Ignore Crockford on this, and put those declarations wherever they make sense.
Right:
var keys = ['foo', 'bar'];
var values = [23, 42];
var object = {};
while (items.length) {
var key = keys.pop();
object[key] = values.pop();
}Wrong:
var keys = ['foo', 'bar'],
values = [23, 42],
object = {},
key;
while (items.length) {
key = keys.pop();
object[key] = values.pop();
}It may be a convention in some third-party code to use a single 'var' statement at top of file to house the 'require' statements, but we would like to move away from that practice since it causes headaches when trying to read diff output.
Variables, properties and function names should use lowerCamelCase. They
should also be descriptive. Single character variables and uncommon
abbreviations should generally be avoided.
Right:
var adminUser = db.query('SELECT * FROM users ...');Wrong:
var admin_user = db.query('SELECT * FROM users ...');Class names should be capitalized using UpperCamelCase.
Right:
function BankAccount() {
}Wrong:
function bank_Account() {
}Constants should be declared as regular variables or static class properties, using lowerCamelCase letters.
Node.js / V8 actually supports mozilla's const extension, but unfortunately that cannot be applied to class
members, nor is it part of any ECMA standard. It also breaks Sonar, so use var instead.
Constants should be used sparingly, and should almost never be singleton values. It's nearly always clearer to use a literal value than to define a constant for it, unless you are doing significant computational work. Maps or collections involving Arrays and Objects are fine, when the situation calls for it.
As a general rule, don't export constants in modules or expose them in classes; that can lead to bad API design.
Exposing module-level globals for reading can sometimes be OK. For writes it is preferred to export a function called
setup(options) which can modify them in bulk.
Right:
var mopToIcon = {
'1000': 'visa.png',
'1001': 'discover.png'
};
function File() {
this.modMs = this.modTime * 1000;
this.effectivePerms = 0777 & this.perms;
}Wrong:
var Second = 1 * 1000;
function File() {
}
File.FULL_PERMISSIONS = 0777;Wrong:
const SECOND = 1 * 1000;
function File() {
}
File.fullPermissions = 0777;Inheritance is tricky, especially when modules are involved, so we'll define the following guidelines.
- Classes are always defined using a named function (see
function Result()below) - That class is always set as a key of
exports; never overwrite the entire module.exports or exports object with a class - Class names and exported classes will be UpperCamelCase, and will have same name
- Always have
var self = this;be the first line of every constructor and class method, and never usethisoutside of that one line; this allows us to categorically avoid weird edge cases around use ofthisin function calls - Always have a single blank line after the above
var self = this;statement - The defintion of the constructor should never end with a semicolon
- Use
util.inheritsto do inheritance (seeutil.inherits(SuccessResult, Result)below); do this before the exports line - Within the constructor of the child class, always run
ChildClass.super_.call, replacing ChildClass appropriately (seefunction SuccessResult()below); unfortunately, the child class must be explicitly named in this.callstatement - This
.callstatement must includeselfas first argument and any remaining functions; if the arguments to child and parent constructor differ significantly, the design is questionable - An acceptable alternative to
.callis to use.apply(self, arguments); this should be done if the arguments do not need to be changed before calling parent; note that we mean the literal automatic Javascript variable namedarguments(seefunction AmbivalentResult()below for an example) - If taking the constructor takes the recommended
optionsargument, take steps to avoid overwriting its members, such as withlodashand its_.extend()function below.
Here is an example of inheritance done correctly:
var _ = require('lodash');
var util = require('util');
//
// Result base class
//
function Result(inOptions) {
var self = this;
_.extend(self, {
detail: null,
message: null,
status: null
}, inOptions);
}
exports.Result = Result;
Result.prototype.getDefaultMessage = function getDefaultMessage() {
var self = this;
return 'unknown error: ' + JSON.stringify(_.pick(self, 'detail', 'status', 'message'));
};
//
// SuccessResult derived class
//
function SuccessResult(inOptions) {
var self = this;
inOptions = _.extend({
detail: settings.serviceName + '_common_success',
message: 'call successful'
}, inOptions);
// Initialize Result superclass
SuccessResult.super_.call(self, inOptions);
self.status = 'success';
// an object to use when returning early from an async flow
self.response = null;
}
util.inherits(SuccessResult, Result);
exports.SuccessResult = SuccessResult;
SuccessResult.prototype.getDefaultMessage = function getDefaultMessage() {
var self = this;
return 'unknown success reason: ' + JSON.stringify(_.pick(self, 'detail', 'status', 'message'));
};
//
// AmbivalentResult derived class
//
function AmbivalentResult(inOptions, existingErr) {
var self = this;
// Initialize Result superclass
AmbivalentResult.super_.apply(self, arguments);
}
util.inherits(AmbivalentResult, Result);
exports.AmbivalentResult = AmbivalentResult;Do not use trailing commas. Do put short declarations on a single line. Only quote keys when your interpreter complains:
Right:
var a = ['hello', 'world'];
var b = {
good: 'code',
'is generally': 'pretty',
'static': 'is a reserved keyword by ECMA'
};Wrong:
var a = [
'hello', 'world'
];
var b = {"good": 'code'
, is generally: 'pretty'
};Don't propagate bad practices from some members of the Perl community. If you find yourself more than 6 columns from your previous line, rethink your actions and prepare for rewrite.
Right:
var collection = {
foo: ['bar', 'baz']
};
var arr = [
'long-element-needing-indentation',
'longer-element-needing-indentation'
];Wrong:
var collection = {
foo: ['bar',
'baz'
]
}
;
var arr = ['long-element-needing-indentation',
'longer-element-needing-indentation'
];The triple equality operator is almost never what you actually mean when writing real programs.
If you want to check whether a field is truthy, omit the operator entirely, like this:
if (apple) {
console.log('apple is truthy');
}If you want to check whether an Object field exists, use lodash library _.has function:
var apple = {
core: 1,
stem: 2
};
if (_.has(apple, 'core')) {
console.log('apple has a core');
}If you want to check whether two variables point to the same Object, use ==:
var apple1 = {
core: 1,
stem: 2
};
var apple2 = {
core: 1,
stem: 2
};
if (apple1 == apple2) {
console.log('apple1 is the same object as apple2');
}You should generally avoid checking deep equality on two objects except in test cases. For those, use a function provided by the test suite:
expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });When you must distinguish between several different falsy values, only then should you use the === operator. Never
use typeof for this purpose.
Right:
var a = 0;
if (a === 0) {
console.log('winning');
}
var b;
if (b === undefined) {
console.log('bi-winning');
}Wrong:
var a = 0;
if (a == 0) {
console.log('losing');
}
var b;
if (typeof b === 'undefined') {
console.log('bi-losing');
}The ternary operator should be split up into multiple lines when any of the terms is complex. If you do this as part of an assignment, line up the '=', '?' and ':' characters.
But it should be kept to a single line when all of the terms are simple.
Parens should not be used around the condition part of the operation (the a == b part below), unless the condition is
complex.
If the ternary operation is part of a string concatenation or array, parens should be placed around the entire constuct.
Right:
var foo = a == b ? 1 : 2;Wrong:
var foo = (a == b)
? 1
: 2;Right:
var foo = apple.length == banana.length * 2
? apple.slice(banana, 2, 1)
: banana.slice(apple, 2, 1);
var pear = { verb: 'crunchy' };
var bar = (pear === undefined ? 'i have no' : 'i have a ' + pear.verb) + ' pear';Wrong:
var foo = (apple.length == banana.length * 2) ? apple.slice(banana, 2, 1) : banana.slice(apple, 2, 1);
var pear = { verb: 'crunchy' };
var bar = ((pear === undefined) ? 'i have no' : ('i have a ' + pear.verb)) + ' pear';Do not extend the prototype of native JavaScript objects. Your future self will be forever grateful.
Right:
var a = [];
if (_.isEmpty(a)) {
console.log('winning');
}Wrong:
Array.prototype.empty = function() {
return !this.length;
}
var a = [];
if (a.empty()) {
console.log('losing');
}Use a single space between ! and the condition.
Right:
var arr = [1];
if (! _.isEmpty(a)) {
console.log('winning');
}
if (! (_.contains(arr, 2) || _.contains(arr, 3))) {
console.log('winning');
}Wrong:
var arr = [1];
if (!_.isEmpty(a)) {
console.log('losing');
}
if (!(_.contains(arr, 2) || _.contains(arr, 3))) {
console.log('losing');
}Any non-trivial conditions should be assigned to a descriptive variable. We define non-trivial to mean: a condition composed of two or less operations.
Exception: If a trivial condition would be used more than once in the same function, or you have multiple similar of them in the same function, then it should be treated as non-trivial and have its own descriptive variable.
Right:
if (user.isAdmin() || user.isModerator()) {
console.log('winning');
}Wrong:
var isAuthorized = (user.isAdmin() || user.isModerator());
if (isAuthorized) {
console.log('losing');
}Right:
var isAuthorized = (user.isAdmin() || user.isModerator());
var isStaff = (user.isProfessor() || user.isJanitor());
if (isAuthorized && isStaff) {
console.log('winning');
}Wrong:
if ((user.isAdmin() || user.isModerator()) && (user.isProfessor() || user.isJanitor())) {
console.log('losing');
}Keep your functions short. A good function fits on a slide that the people in the last row of a big room can comfortably read. So don't count on them having perfect vision and limit yourself to ~15 lines of code per function.
If you are doing operations requiring control flow, however, it is preferable to have a large outer function that contains state and small inner functions which manipulate that state when callbacks finish. Be sure to name these functions.
While deep nesting of if-statements should be avoided, avoid return statements when possible, as overusing them can
mask problems.
It's easier to reason about callback behavior and catch control flow problems when you use if/else in preference to
if/return. Due to node.js being so callback-focused, this means you should almost never use return.
For complex computational work that does not involve callbacks, return statements are preferable to deeply-nested
if statements.
Right:
if (errorHappened) {
callback(new Error('error happened'));
} else {
callback(null, 'winning');
}Wrong:
if (errorHappened) {
callback(new Error('error happened'));
return;
}
// oops, forgot my callback
console.log('losing');
// add it back after my service crashes
callback(null, 'still losing');Right:
function isPercentage(val, callback) {
if (val >= 0) {
if (val < 100) {
callback(true);
} else {
callback(false);
}
} else {
callback(false);
}
}Wrong:
function isPercentage(val, callback) {
if (val < 0) {
callback(false);
return;
}
if (val > 100) {
callback(false);
return;
}
callback(true);
}For this particular example it is preferable to shorten things even further, and minimize the number of code paths which can execute callbacks:
Even better:
function isPercentage(val) {
var isInRange = (val >= 0 && val <= 100);
callback(isInRange);
}Functions should always be named. This is for clarity in both the code itself and in stacktraces when an exception occurs. It also results in better heap and CPU profiles, which is crucial for tracking own problems. Named functions are also more performant than anonymous functions that are not named.
When naming functions, strive for a balance of being both succinct and descriptive.
(While writing tests, it is okay for functions to not be named (ie. it('should do ...', function() { ...). Mocha
makes heavy use of anonymous functions as callbacks, and those functions are just a means to an end for writing tests.)
Extremely trivial one-line functions can also sometimes go without names.
Right:
var jadeFiles = _.filter(filenames, function jadeOnly(filename) {
return _s.strRightBack(filename, '.') === 'jade';
});Wrong:
var jadeFiles = _.filter(filenames, function(filename) {
return _s.strRightBack(filename, '.') === 'jade';
});Also Wrong:
var jadeFiles = _.filter(filenames, function filterJadeFilesByExtension(filename) {
return _s.strRightBack(filename, '.') === 'jade';
});Use function closures, but don't nest them overly. Otherwise your code will become a mess. Nested functions also perform badly, so try to keep functions defined at the top level (or within a class prototype) when reasonable.
If you have multiple asynchronous code blocks, use async.series() and safe-callback to split the code into tasks.
Right:
setTimeout(function later() {
client.connect(afterConnect);
}, 1000);
function afterConnect() {
console.log('winning');
}Also Right:
setTimeout(function later() {
client.connect(function() {
console.log('still winning');
});
}, 1000);Wrong:
setTimeout(function() {
request('url',
function() {
client.connect(function() {
console.log('losing');
})
})
);
}, 1000);Use slashes for both single line and multi line comments. Try to write comments that explain higher level mechanisms or clarify difficult segments of your code. Don't use comments to restate trivial things. Comments must have a single space between the slash and text.
Right:
// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE'', 'SOMETHING', 'VALUE']
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));
// This function has a nasty side effect where a failure to increment a
// redis counter used for statistics will cause an exception. This needs
// to be fixed in a later iteration.
function loadUser(id, cb) {
// ...
}
var isSessionValid = (session.expires < Date.now());
if (isSessionValid) {
// ...
}Wrong:
// Execute a regex
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));
// Usage: loadUser(5, function() { ... })
function loadUser(id, cb) {
// ...
}
// Check if the session is valid
var isSessionValid = (session.expires < Date.now());
// If the session is valid
if (isSessionValid) {
// ...
}Also Wrong:
//Execute a regex
var matches = item.match(/ID_([^\n]+)=([^\n]+)/));If you have a requirement to write documentation on usage of an entire codebase, then use a README.md file or internal site. If your intended audience is internal-only, you should prefer to write documentation hosted on an internal site, and you should not use README.md files.
Your co-workers don't care about running "make docs", and care even less to read your entire source tree to understand how to start your service.
If your code is for a standalone script, it may make sense to support the --usage parameter, in which case
documentation should be code, not comments.
Wrong:
/**
NAME
Web application for the Front End SOA
SYNOPSIS
From the command-line
_(Only file-based configuration is considered.)_
$ node myservice
Myservice is listening on port 8080 ...
**/
exports.init = init;If the purpose of a function or class is clear from name or code, then documentation may omitted.
For nonobvious functions: documentation should only cover immediate use of functions. For classes: it may have a brief overview of all functions which are properties of the class, as a means of describing how the class is meant to be used.
For both: It should not be overly verbose. It should come just before the definition of the function or class. It should not be at the beginning or end of the file. It should never have placeholder garbage like "SYNOPSIS" in POD docs; usage of any POD constructs in documentation is a failure.
If the documentation approaches 75% of the length of the code, or the code is too complex to explain succinctly, then you should probably move the code into its own node.js module with separate code repo, and move the documentation into internal website or README.md file for that module.
Linking to internal sites (like Jira) in comments about code changes can be OK, but only if the change is non-obvious product logic. Such links must be accompanied with a brief, clear, 1-2 line description of the product requirement. Avoid having too many of them in the same part of the codebase; prefer to replace old ones if requirements have changed.
Crazy shit that you will probably never need. Stay away from it.
Do not use setters, they cause more problems for people who try to use your software than they can solve.
Feel free to use getters that are free from side effects, like providing a length property for a collection class.
