This is a minimal and opinionated guide for writing consistent and productive Node.Js and Express.js code. It is inspired by what has proven to be a consistent way of structuring code and has worked well in other mature languages and frameworks.
// Asynchronous errors, The WRONG way
fs.open("file.txt", function (err, contents) {
console.log( contents );
});
// Asynchronous errors, The Right way
fs.open("file.txt", function (err, contents) {
if (err) {
console.error("An error occurred!", err);
} else {
console.log(contents);
}
});
// Use Try-Catch for Synchronous Calls
try {
console.log("entering try block");
throw "thrown message"; // Synchronous Method Throws Error
console.log("this message is never seen");
}
catch (e) {
console.log("entering catch block");
console.log(e);
console.log("leaving catch block");
}
finally {
console.log("entering and leaving the finally block");
}
In most of the Node core APIs, try/catch cannot be used to properly handle exceptions for all others utilize try-catch. At project level handling errors needs to be a mandate.
// Wrong
// nodeService.js
(function(){
var getHttpData = function(){
console.log('My Data');
};
module.exports = getHttpData;
})();
// Better
// nodeService.js
var getHttpData = function(){
console.log('My Data');
};
module.exports = getHttpData;
From NodeJS Documentation:
"Variables local to the module will be private, as though the module was wrapped in a function"
In Javascript Indentation is for aesthetics and makes your code more readable and hence should be implemented as such. It should not Impact space utlization or productivity
// Use of single quotes for variables
var requestType = 'POST';
// Acceptable use of double quotes in a js config file
var SAMPLE_DIALOGUE_EN = [
"learning the language as it's awesome!",
'and he proclaimed, "so it shall be!" ',
];
For a consistent codebase a mix of a double quotes and single quotes should be avoided
// Error prone and not readable
if (true)
{
console.log('winning');
}
// Returns undefined
return
{
key: 'value'
}
// The right way
if (true) {
console.log('winning');
}
// Returns { key: 'value' }
return {
key: 'value'
}
The not-a-good-way consumes more space and can fail in certain scenerios
// Not too elegant
var foo = (a === b)
? 1
: 2;
// Too complex
var foo = ( a === b ) ? bar : ( foo !==1 ? bar1 : a );
// Simple and readable
var foo = ( a === b ) ? 1 : 2;
// Simplify ternary when they are too complex
var foo = ( a === b ) ?
bar : (
(foo !==1) ? bar1 : a
);
// Or split into if-else-ternary (Remember: Can impact performance in loops though)
if( a === b ){
foo = bar;
}else{
foo = ( foo !== 1 ) ? bar1 : a;
}
Single-line ternary operators are fine as long as they are not too complex in that case divide them into if-else blocks
//Wastes Space, too much typing;
var PI = 3.14159265;
var PHI = 1.618;
var HTTP_PORT = 80;
var SSH_PORT = 22;
//Better Way, Readable and concise (Group Logical Constants Together)
var PI = 3.14159265,
PHI = 1.618;
var HTTP_PORT = 80,
SSH_PORT = 22;
var SERVER_A, SERVER_B;
Grouping Declarations when possible makes it easier to categorize Constants under categories, don't group when the constants are not logically connected
// Not so elegant
req.on('end', function() {
console.log('losing');
});
// Not so elegant either
setTimeout(function() {
client.connect(function() {
console.log('losing');
});
}, 1000);
// Name the Closures/Functions
var onConnectionEnd = function() {
console.log('winning');
}
req.on('end', onConnectionEnd);
//Similarly
var afterConnect = function() {
console.log('winning');
}
setTimeout(function() {
client.connect( afterConnect );
}, 1000);
Name your closures so they can be re-used and are easy to maintain
/*
Illustrates line wrapping for long param/return descriptions.
@param {string} guid
Accepts GUID as a parameter, where guid is a 12 digit Hexadceimal number
GUID is also a valid mongodb Object ID
@return {OBJECT}
{OBJECT}
{
// Policy ID
pid: {NUMBER},
// Persona ID
perID: {NUMBER}
}
*/
/*
Illustrates line wrapping for long param/return descriptions.
@param {string} guid
Accepts GUID as a parameter, where guid is a 12 digit Hexadceimal number
GUID is also a valid mongodb Object ID
@return {PROMISE} {OBJECT|ARRAY|NULL}
{OBJECT}
{
gid : {NUMBER},
userDetails: {OBJECT},
userPersonaID: {NUMBER},
userPolicyID: {NUMBER}
}
{ARRAY}
returns an Array of gids
{NULL}
if promise fails returns NULL
*/
Customized JSDoc format for Function Commenting for the purpose of documentation
// The Wrong Way for Variables/Properties/Functions
//PHP/CodeIgniter Naming Convention (Don't Use in Node)
var admin_user = req.session.body(..);
// Pascal Case naming convention (Don't use for variables, properties or functions)
var AdminUser = req.session.body(..);
//JQuery Naming convention (Don't use in Node)
var $adminUser = req.session.body(..);
// The Right Way for Variables/Properties/Functions A.K.A. lowerCamelCase in Node and Javascript
var adminUser = req.session.body(..);
var myFunction = function(){};
var myObj = {};
myObj.myProperty = 123;
//Classes
var MyClass = App.factory('myFactory', function(){...});
//Constants
var PI = 3.1415;
// Or define constants in object (Remember: Object lookup is slower than variables though)
var MATH_CONSTANTS = {
PHI : 1.618
};
Javascript uses Camelcase naming convention and is an industry standard so following that makes more sense as usually the camelcase is something most JS developers don't debate on and mostly stick to it.
// Sample Project Structure
//==========================
SampleApp-Server/
- .git
- .gitignore
- package.json
- app.js // Cluster Master, our main file that runs server.js
- server.js // Cluster Node file
- config/
- base.js // Base config file
- env/ // Environment specific config files
- development-e0.js
- development-e1.js
- production-e2.js
...
- bootstrap/
- clusterHealthMonitor.js
- customGarbageCollector.js
...
- routes/
- v1/
- homeRouter.js
...
- middlewares/
- session.js
- winston.js
...
- data/
- mockPznData.json
...
- models/
- userModel.js
...
- controllers/
- homeController.js
...
- services/
- pznDataService.js
...
- schemas/
- dataModelSchema/
- userSchema.js
...
- requestSchema/
- contentSchema.js
...
...
- utils/
- common/
- httpRequest.js
...
- formatConversion/
- pznXml2PznJson.js
...
- libs/
- amazonS3HMACGenerator.js
...
- tests/
...
- .gulpfile // Our Gulpfile
LOG LEVELS
==================================================
# DEVELOPMENT
--------------------------------------------------
> INFO
> TRACE
> DEBUG
> WARN
> ERROR
> FATAL
# PRODUCTION
--------------------------------------------------
> INFO
> TRACE
> WARN
> ERROR
> FATAL
INFORMATION TYPES
==================================================
# Standard Information
--------------------------------------------------
> Server Diagnostics information
> Uncaught/Global Exceptions and errors
> Client data
> Request Data
> Response Data
> Global warnings
# Application Information
--------------------------------------------------
> Datasource Activity
> Application Exceptions
> Application level warnings
> Page errors
DESCRIPTIONS
===================================================
# Server Diagnostics information
---------------------------------------------------
> [INFO] IP and Port server is listening on
> [INFO] The environment server is running in ( Development, Production etc.. )
> [DEBUG] Number of threads/nodes to be spawned
> [DEBUG] Number of file descriptors available (when applicable/available)
# Uncaught/Global Exceptions and errors
---------------------------------------------------
> [ERROR] Error/Exception Description
> [DEBUG] Error/Exception Stack Trace
# Client's Info
---------------------------------------------------
> [INFO] Client's IP Address
> [DEBUG] Client's Useragent
# Request Data
---------------------------------------------------
> [TRACE] URL tried to be accessed
> [DEBUG] Request Params *Masked*
# Response Data
---------------------------------------------------
> [DEBUG] Response *masked*
# Global warnings
---------------------------------------------------
> [WARN] Server running in development mode warning
# Datasource Activity
---------------------------------------------------
> [TRACE] Datasource activity description
> [DEBUG] SQL Queries, Mongo/Redis inserts, delete, update logs
# Application Exceptions
---------------------------------------------------
> [ERROR] Application-Level Error/Exception Description
> [DEBUG] Application-Level Error/Exception Stack Trace
# Application level warnings
---------------------------------------------------
> [WARN] Datasource not available
# Page errors
---------------------------------------------------
> [TRACE] 404, 500 ,... Errors
Attributes (Followed by a separation character "-")
====================================
> SERVER_TIMESTAMP
> CLUSTER_NODE_ID
> LOG_LEVEL
> EVENT_CODE
> SESSION_ID
> MESSAGE
Description
====================================
# SERVER_TIMESTAMP
Server Timestamp in local server time format (GMT or UTC) in ISO 8601 Format
(Prefer to change server time to UTC if the logs are pulled to a central location for analysis)
# CLUSTER_NODE_ID
If Node is running in a clustered mode logging the node id helps to make sense of logs if all cluster nodes are logging in the same file or at the same time/standard output
# LOG_LEVEL
Log levels described above
# EVENT_CODE
Generic Event Codes such as DB, USER, MONGO
# SESSION_ID
When available log sesson id for the client making the request
# MESSAGE
Actual Message describing the event
Example Log
=====================================
2016-03-07T22:19:52+00:00 - Master - info - APP - App Started
2016-03-07T22:19:52+00:00 - Master - info - APP - Now listening at http://:::5000
2016-03-07T22:19:52+00:00 - Master - info - APP - Running in development mode
2016-03-07T22:19:52+00:00 - Master - debug - APP - Spawning Nodes : total 2 available cores
2016-03-07T22:19:52+00:00 - Node 1 - debug - APP - Node 1 Alive
2016-03-07T22:19:52+00:00 - Node 1 - debug - MONGO - Connection to Mongo successful
2016-03-07T22:19:52+00:00 - Node 2 - debug - APP - Node 2 Alive
2016-03-07T22:19:52+00:00 - Node 2 - debug - MONGO - Connection to Mongo successful
2016-03-07T22:19:52+00:00 - Node 1 - info - SERVER - SESSION_ID_::asdf34j2kgjh4asf3ffdfg - Requested received from IP: 192.168.1.1
2016-03-07T22:19:52+00:00 - Node 1 - trace - SERVER - SESSION_ID_::asdf34j2kgjh4asf3ffdfg - Requested for: /private/setPersona
2016-03-07T22:19:52+00:00 - Node 1 - debug - SERVER - SESSION_ID_::asdf34j2kgjh4asf3ffdfg - Requested params: {"perid":"56d6131deac355c92351609f"}
// Faster
function ClassicObject() {
this.x = 10;
}
ClassicObject.prototype.getX = function () {
return this.x;
};
// Slower
function ClosureObject() {
var x = 10; // Stored in context as it is used in closure below
return {
getX: function () {
return x;
}
};
}
// Switch Statement slower than Objects Due to a large number of items and more jump statements
switch (node.tagName) {
case goog.dom.TagName.APPLET:
case goog.dom.TagName.AREA:
case goog.dom.TagName.BR:
case goog.dom.TagName.COL:
case goog.dom.TagName.FRAME:
case goog.dom.TagName.HR:
case goog.dom.TagName.IMG:
case goog.dom.TagName.INPUT:
case goog.dom.TagName.IFRAME:
case goog.dom.TagName.ISINDEX:
case goog.dom.TagName.LINK:
case goog.dom.TagName.NOFRAMES:
case goog.dom.TagName.NOSCRIPT:
case goog.dom.TagName.META:
case goog.dom.TagName.OBJECT:
case goog.dom.TagName.PARAM:
case goog.dom.TagName.SCRIPT:
case goog.dom.TagName.STYLE:
return false;
}
return true;
var takesChildren = {}
takesChildren[goog.dom.TagName.APPLET] = 1;
takesChildren[goog.dom.TagName.AREA] = 1;
...
// Performs Better than the Switch Statement
return !takesChildren[node.tagName];
// Slower
// V8 Has no idea about the Array type
var a = new Array();
a[0] = 77; // Allocates the array as an Integer Array
a[1] = 88;
a[2] = 0.5; // Allocates, converts to Double Array
a[3] = true; // Allocates, converts to an Array that supports booleans
// Faster
// V8 Knows that this is an Array that will support multiple types and doesn't convert
var a = [77, 88, 0.5, true];
Grokking V8 closures for fun (and profit?)
Long Live Callbacks
On the performance of closures in v8
Google Closure: How not to write Javascript
Deprecating the switch statement for object literals
Performance Tips for JavaScript in V8