This article is a two-part “NodeJS Best Practices” tutorial.

Ok,

People always ask us about our best practices and techniques for Node.js, so in this article we’ll explain and summarize our experience writing code at RisingStack.

One part of node.js’s best practice is coding specifications, the other part is dealing with the development process.

Coding standards

The callback routine

The module should expose an error-first callback interface.

Like this:

Module.exports = function (dragonName, callback) {var dragon = createDragon(dragonName); Callback (null, dragon); callback(dragon); }Copy the code

Be sure to check for error messages in the callback

To better understand why you have to do this, find a way to create an example that will fail, and then fix it.

Var fs = require('fs'); var fs = require('fs'); function readJSON(filePath, callback){ fs.readFile(filePath, function(err, data) { callback(JSON.parse(data)); }); } readJSON('./package.json', function (err, pkg) { ... }Copy the code

The first problem was the readJSON function, which had an error during execution and which did no error checking. Make sure you check for errors first.

Improvement plan:

// This example will still die, it will be fixed soon! function readJSON(filePath, callback) { fs.readFile(filePath, function(err, If (err) {// If (err) {// If (err) {// If (err) {// If (err) {// If (err) {// If (err) {// If (err) {// If (err) { } // Null and JSON callback(null, json.parse (data)) are passed if there are no errors; }); }Copy the code

Returns the callback function

The error in the example above is that if the error occurs, the expression in if will not stop running, but will continue running. This can lead to a lot of unknown errors. To make a long story short, be sure to return via the callback function.

// This example will still hang, fix it immediately! function readJSON(filePath, callback) { fs.readFile(filePath, function(err, data) { if (err) { return callback(err); } return callback(null, JSON.parse(data)); }); }Copy the code

Use try-catch only in synchronized code

Almost perfect! But there’s one more thing we have to be careful about with json.parse. When you call json.parse, an exception is thrown if the string passed in cannot be parsed into JSON format.

Since json.parse happens synchronously, we can wrap it in a try-catch. Note that you can only do this with synchronized code blocks, not callback functions.

Function readJSON(filePath, callback) {fs.readFile(filePath, function(err, data) {var parsedJson; If (err) {return callback(err); } // parse JSON try {parsedJson = json.parse (data); } catch (exception){ return callback(exception); } return callback(null, parsedJson); }); }Copy the code

Avoid this and new as much as possible

Because Node involves a lot of callbacks and heavily uses higher-order functions to control the flow, binding a specific context in Node is not always effective. Using a functional programming style can save you a lot of trouble.

Of course, prototypes can be more efficient in some situations, but avoid them whenever possible.

Creating a Micro Module

In Unix mode:

Developers should build a program by breaking it up into many simple modules that are integrated by well-defined interfaces, so problems are local and new features can be added in future releases by replacing parts of the program.

Don’t create monster code, keep it simple, one module does one thing, but do it to perfection.

Use good asynchronous patterns

Use the async asynchronous processing module.

Error handling

Errors can be divided into two parts, operational errors and programming errors.

The operating error

Errors can occur in well-written applications as well. Because these are not bugs, they are caused by the operating system or remote services, for example:

Handling operation error

Depending on the type of runtime error, you can handle it as follows:

  • Try to resolve errors – if the file is missing, you can create one ahead of time.
  • When dealing with network traffic, you can retry the operation.
  • Telling the customer about the problem indicates that something is not working properly — it can be used to process user input.
  • If the error cannot be resolved under the current condition, terminate the process, for example, if the application cannot read its configuration file.

Also, all of the above actions should be logged.

Programming errors

Programming errors are bugs. Here are a few things you should avoid, such as:

  • There is no callback when an asynchronous function is called.
  • Cannot read undefined property

Handling programming errors

If the error is a bug, terminate the application immediately because you don’t know the current running state of the application. The process control system should be able to restart the application when an error occurs, such as the supervisord or Monit.

Workflow techniques

Create a new project using NPM init

The init command helps you create the package.json configuration file for your application. The file sets some default configurations that can be modified later.

Creating a good project starts like this:

mkdir my-awesome-new-project 
cd my-awesome-new-project 
npm initCopy the code

Specify start and test scripts.

In your package.son file, you can set the script in the scripts section. NPM init creates two scripts by default, start and test. It can be run using the NPM start and NPM test commands.

And, as a bonus: you can add custom scripts here, run them from NPM run-script.

Note that NPM scans all executable scripts under node_modules/.bin by setting $PATH. This avoids installing the global NPM module.

The environment variable

Both production deployment and demonstration deployment should be implemented by environment variables. The most popular implementation is to set the NODE_ENV variable in both production and demo.

Depending on the environment variables you set, you can use the nconf module to load configuration information.

Of course, you can also use other environment variables in your Node.js application to set process.env, which is an object containing the user environment.

Don’t reinvent the wheel

Make it a priority to find an off-the-shelf solution. NPM has a huge library that covers most of the features you would normally need.

Use style Guide

Keeping all code in a uniform style helps to understand a large code base. This should include indentation, variable naming, best practices, and more.

For a practical example, check out the Node.js style guide written by RisingStack.

Afterword.

I hope this article helped you write Node.js, and solved some of your headaches. The next article will continue to explore tips and best practices.

You can read about deployment tips from continuously deploying Node.js applications. Also, we’re hosting a Node Conference (NodeConf) called One-Shot Budapest on November 21st. Follow PayPal, NPM, Strongloop to share your experience. I hope to see you then.

The next

You’ll remember our node.js best practices from the previous article. In this article we continue to discuss more best practices that can help you become a better Node.js developer.

Keep your style consistent

When developing JavaScript applications on a large team, it is important to create a style guide that everyone follows. If you’re looking for inspiration, I recommend reading RisingStack’s Node.js style guide.

But that’s just the start — when you set the standards, all team members must follow the style guidelines. This is how JSCS were born.

JSCS is a JavaScript coding style checker. Adding JSCS to the project is a piece of cake for you:

npm install jscs --save-devCopy the code

The next key you need to do is to add the following code to the package.json file to enable it:

scripts: {  
    "jscs": "jscs index.js"
}Copy the code

Of course, you can also add multiple file and directory checks. But why did we just create a custom script in the package.json file? We installed JSCS locally, so there can be multiple versions on a single system. This works because node_modules/.bin is set to PATH when NPM executes.

You can set your validation rules in the .jscsrc file, or use a preset. You can find the available presets here, and can use them with –preset=[PRESET_NAME]. You can define validation rules in the.jscsrc file, or use default rules. Here you can view available presets, which are applied with –preset=[PRESET_NAME].

Execute JSHint and JSCS rules

Your build process should also include JSHint and JSCS, but it might be a good idea to run pre-commit Checks on your developer’s computer.

To do this easily, you can use the pre-commit NPM library:

npm install --save-dev pre-commitCopy the code

Then do the following configuration in package.json:

pre-commit": [  
    "jshint",
    "jscs"
],Copy the code

Note that pre-commit will scan all scripts in package.json. With this enabled, it is automatically checked every time you commit.

Replace JSON with JS for configuration

We see a lot of projects configured using JSON files. This is by far the most common practice, and JS profiles provide greater flexibility. So we recommend that you use the config.js file:

var url =require('url'); var config = module.exports = {}; var redisToGoConfig; Config. Server = {host: '0.0.0.0, port: process. The env. The port | | 3000}; // look, a comment in the config file! // would be tricky in a JSON ;) config.redis = { host: 'localhost', port: 6379, options: { } }; if (process.env.REDISTOGO_URL) { redisToGoConfig = url.parse(process.env.REDISTOGO_URL); config.redis.port = redisToGoConfig.port; config.redis.host = redisToGoConfig.hostname; config.redis.options.auth_pass = redisToGoConfig.auth.split(':')[1]; }Copy the code

Use the NODE_PATH

Have you ever come across the following situation?

var myModule = require('.. /.. /.. /.. /lib/myModule'); myModule.doSomething(function (err) { });Copy the code

Module dependencies can be troublesome when your project structure becomes complex. There are two ways to solve this problem:

  • Soft link your modules to the node_modules directory.
  • Use the NODE_PATH.

In RisingStack we use the NODE_PATH approach because soft linking all relevant files to the node_modules directory requires a lot of extra work and doesn’t work on many operating systems.

Set NODE_PATH

Suppose your project is structured like this:

Review images

Instead of using relative paths, we can use NODE_PATH pointing to the lib directory. In the start script section of our package.json, we use the NODE_PATH setting and run the project with NPM start.

var Car = require('model/Car'); console.log('I am a Car! '); {" name ":" node_path ", "version" : "1.0.0", "description" : ""," main ":" index. Js ", "scripts" : {" start ": "NODE_PATH=lib node index.js" }, "author": "", "license": "ISC" }Copy the code

Dependency injection

Dependency injection (DI) is a software design pattern in which one or more dependencies (or services) are injected or introduced by reference into the object of dependency.

Dependency injection is very useful in testing. Using this pattern you can easily simulate dependencies between modules.

function userModel (options) { var db; if (! options.db) { throw new Error('Options.db is required'); } db = options.db; return { create: function (done) { db.query('INSERT ... ', done); } } } module.exports = userModel;Copy the code
var db = require('db');
// do some init here, or connect
db.init();

var userModel = require('User')({
db: db
});

userModel.create(function (err, user) {
});Copy the code
var test = require('tape'); var userModel = require('User'); test('it creates a user with id', function (t) { var user = { id: 1 }; var fakeDb = { query: function (done) { done(null, user); } } userModel({ db: fakeDb }).create(function (err, user) { t.equal(user.id, 1, 'User id should match'); t.end(); })});Copy the code

In the above example we have two failed db’s. In the index.js file is the “real” DB module, whereas in the second code we simply create a mock DB module.

This allows us to easily introduce simulated dependencies into the module during testing.