Since the moment Node.js was unveiled to the world, it has seen a fair share of both praise and criticism. The debate still continues, and may not end anytime soon. What we often overlook in these debates is that every programming language and platform is criticized based on certain issues, which are created by how we use the platform. Regardless of how difficult Node.js makes writing safe code, and how easy it makes writing highly concurrent code, the platform has been around for quite a while and has been used to build a huge number of robust and sophisticated web services. These web services scale well, and have proven their stability through their endurance of time on the Internet.

However, like any other platform, Node.js is vulnerable to developer problems and issues. Some of these mistakes degrade performance, while others make Node.js appear straight out unusable for whatever you are trying to achieve. In this article, we will take a look at ten common mistakes that developers new to Node.js often make, and how they can be avoided to become a Node.js pro.

node.js developer mistakes

Mistake #1: Blocking the event loop

JavaScript in Node.js (just like in the browser) provides a single threaded environment. This means that no two parts of your application run in parallel; instead, concurrency is achieved through the handling of I/O bound operations asynchronously. For example, a request from Node.js to the database engine to fetch some document is what allows Node.js to focus on some other part of the application:

// Trying to fetch an user object from the database. Node.js is free to run other parts of the code from the moment this function is invoked..
db.User.get(userId, function(err, user) {
	// .. until the moment the user object has been retrieved here
})

node.js single threaded environment

However, a piece of CPU-bound code in a Node.js instance with thousands of clients connected is all it takes to block the event loop, making all the clients wait. CPU-bound codes include attempting to sort a large array, running an extremely long loop, and so on. For example:

function sortUsersByAge(users) {
	users.sort(function(a, b) {
		return a.age < b.age ? -1 : 1
	})
}

Invoking this “sortUsersByAge” function may be fine if run on a small “users” array, but with a large array, it will have a horrible impact on the overall performance. If this is something that absolutely must be done, and you are certain that there will be nothing else waiting on the event loop (for example, if this was part of a command-line tool that you are building with Node.js, and it wouldn’t matter if the entire thing ran synchronously), then this may not be an issue. However, in a Node.js server instance trying to serve thousands of users at a time, such a pattern can prove fatal.

If this array of users was being retrieved from the database, the ideal solution would be to fetch it already sorted directly from the database. If the event loop was being blocked by a loop written to compute the sum of a long history of financial transaction data, it could be deferred to some external worker/queue setup to avoid hogging the event loop.

As you can see, there is no silver-bullet solution to this kind of Node.js problem, rather each case needs to be addressed individually. The fundamental idea is to not do CPU intensive work within the front facing Node.js instances - the ones clients connect to concurrently.

Mistake #2: Invoking a Callback More Than Once

JavaScript has relied on callbacks since forever. In web browsers, events are handled by passing references to (often anonymous) functions that act like callbacks. In Node.js, callbacks used to be the only way asynchronous elements of your code communicated with each other - up until promises were introduced. Callbacks are still in use, and package developers still design their APIs around callbacks. One common Node.js issue related to using callbacks is calling them more than once. Typically, a function provided by a package to do something asynchronously is designed to expect a function as its last argument, which is called when the asynchronous task has been completed:

module.exports.verifyPassword = function(user, password, done) {
	if(typeof password !== ‘string’) {
		done(new Error(‘password should be a string’))
		return
	}

	computeHash(password, user.passwordHashOpts, function(err, hash) {
		if(err) {
			done(err)
			return
		}
		
		done(null, hash === user.passwordHash)
	})
}

Notice how there is a return statement every time “done” is called, up until the very last time. This is because calling the callback doesn’t automatically end the execution of the current function. If the first “return” was commented out, passing a non-string password to this function will still result in “computeHash” being called. Depending on how “computeHash” deals with such a scenario, “done” may be called multiple times. Anyone using this function from elsewhere may be caught completely off guard when the callback they pass is invoked multiple times.

Being careful is all it takes to avoid this Node.js error. Some Node.js developers adopt a habit of adding a return keyword before every callback invocation:

if(err) {
	return done(err)
}

In many asynchronous functions, the return value has almost no significance, so this approach often makes it easy to avoid such a problem.

Mistake #3: Deeply Nesting Callbacks

Deeply-nesting callbacks, often referred to as “callback hell”, is not a Node.js issue in itself. However, this can cause problems making code quickly spin out of control:

function handleLogin(..., done) {
	db.User.get(..., function(..., user) {
		if(!user) {
			return done(null, ‘failed to log in’)
		}
		utils.verifyPassword(..., function(..., okay) {
			if(okay) {
				return done(null, ‘failed to log in’)
			}
			session.login(..., function() {
				done(null, ‘logged in’)
			})
		})
	})
}

The more complex the task, the worse this can get. By nesting callbacks in such a way, we easily end up with error-prone, hard to read, and hard to maintain code. One workaround is to declare these tasks as small functions, and then link them up. Although, one of the (arguably) cleanest solutions to this is to use a utility Node.js package that deals with asynchronous JavaScript patterns, such as Async.js:

function handleLogin(done) {
	async.waterfall([
		function(done) {
			db.User.get(..., done)
		},
		function(user, done) {
			if(!user) {
			return done(null, ‘failed to log in’)
			}
			utils.verifyPassword(..., function(..., okay) {
				done(null, user, okay)
			})
		},
		function(user, okay, done) {
			if(okay) {
				return done(null, ‘failed to log in’)
			}
			session.login(..., function() {
				done(null, ‘logged in’)
			})
		}
	], function() {
		// ...
	})
}

Similar to “async.waterfall”, there are a number of other functions that Async.js provides to deal with different asynchronous patterns. For brevity, we used simpler examples here, but reality is often worse.

Mistake #4: Expecting Callbacks to Run Synchronously

Asynchronous programming with callbacks may not be something unique to JavaScript and Node.js, but they are responsible for its popularity. With other programming languages, we are accustomed to the predictable order of execution where two statements will execute one after another, unless there is a specific instruction to jump between statements. Even then, these are often limited to conditional statements, loop statements, and function invocations.

However, in JavaScript, with callbacks a particular function may not run well until the task it is waiting on is finished. The execution of the current function will run until the end without any stop:

function testTimeout() {
	console.log(“Begin”)
	setTimeout(function() {
		console.log(“Done!”)
	}, duration * 1000)
	console.log(“Waiting..”)
}

As you will notice, calling the “testTimeout” function will first print “Begin”, then print “Waiting..” followed by the the message “Done!” after about a second.

Anything that needs to happen after a callback has fired needs to be invoked from within it.

Mistake #5: Assigning to “exports”, Instead of “module.exports”

Node.js treats each file as a small isolated module. If your package has two files, perhaps “a.js” and “b.js”, then for “b.js” to access “a.js”’s functionality, “a.js” must export it by adding properties to the exports object:

// a.js
exports.verifyPassword = function(user, password, done) { ... }

When this is done, anyone requiring “a.js” will be given an object with the property function “verifyPassword”:

// b.js
require(‘a.js’) // { verifyPassword: function(user, password, done) { ... } } 

However, what if we want to export this function directly, and not as the property of some object? We can overwrite exports to do this, but we must not treat it as a global variable then:

// a.js
module.exports = function(user, password, done) { ... }

Notice how we are treating “exports” as a property of the module object. The distinction here between “module.exports” and “exports” is very important, and is often a cause of frustration among new Node.js developers.

Mistake #6: Throwing Errors from Inside Callbacks

JavaScript has the notion of exceptions. Mimicking the syntax of almost all traditional languages with exception handling support, such as Java and C++, JavaScript can “throw” and catch exceptions in try-catch blocks:

function slugifyUsername(username) {
	if(typeof username === ‘string’) {
		throw new TypeError(‘expected a string username, got '+(typeof username))
	}
	// ...
}

try {
	var usernameSlug = slugifyUsername(username)
} catch(e) {
	console.log(‘Oh no!’)
}

However, try-catch will not behave as you might expect it to in asynchronous situations. For example, if you wanted to protect a large chunk of code with lots of asynchronous activity with one big try-catch block, it wouldn’t necessarily work:

try {
	db.User.get(userId, function(err, user) {
		if(err) {
			throw err
		}
		// ...
		usernameSlug = slugifyUsername(user.username)
		// ...
	})
} catch(e) {
	console.log(‘Oh no!’)
}

If the callback to “db.User.get” fired asynchronously, the scope containing the try-catch block would have long gone out of context for it to still be able to catch those errors thrown from inside the callback.

This is how errors are handled in a different way in Node.js, and that makes it essential to follow the (err, …) pattern on all callback function arguments - the first argument of all callbacks is expected to be an error if one happens.

Mistake #7: Assuming Number to Be an Integer Datatype

Numbers in JavaScript are floating points - there is no integer data type. You wouldn’t expect this to be a problem, as numbers large enough to stress the limits of float are not encountered often. That is exactly when mistakes related to this happen. Since floating point numbers can only hold integer representations up to a certain value, exceeding that value in any calculation will immediately start messing it up. As strange as it may seem, the following evaluates to true in Node.js:

Math.pow(2, 53)+1 === Math.pow(2, 53)

Unfortunately, the quirks with numbers in JavaScript doesn’t end here. Even though Numbers are floating points, operators that work on integer data types work here as well:

5 % 2 === 1 // true
5 >> 1 === 2 // true

However, unlike arithmetic operators, bitwise operators and shift operators work only on the trailing 32 bits of such large “integer” numbers. For example, trying to shift “Math.pow(2, 53)” by 1 will always evaluate to 0. Trying to do a bitwise-or of 1 with that same large number will evaluate to 1.

Math.pow(2, 53) / 2 === Math.pow(2, 52) // true
Math.pow(2, 53) >> 1 === 0 // true
Math.pow(2, 53) | 1 === 1 // true

You may rarely need to deal with large numbers, but if you do, there are plenty of big integer libraries that implement the important mathematical operations on large precision numbers, such as node-bigint.

Mistake #8: Ignoring the Advantages of Streaming APIs

Let’s say we want to build a small proxy-like web server that serves responses to requests by fetching the content from another web server. As an example, we shall build a small web server that serves Gravatar images:

var http = require('http')
var crypto = require('crypto')

http.createServer()
.on('request', function(req, res) {
	var email = req.url.substr(req.url.lastIndexOf('/')+1)
	if(!email) {
		res.writeHead(404)
		return res.end()
	}

	var buf = new Buffer(1024*1024)
	http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {
		var size = 0
		resp.on('data', function(chunk) {
			chunk.copy(buf, size)
			size += chunk.length
		})
		.on('end', function() {
			res.write(buf.slice(0, size))
			res.end()
		})
	})
})
.listen(8080)

In this particular example of a Node.js problem, we are fetching the image from Gravatar, reading it into a Buffer, and then responding to the request. This isn’t such a bad thing to do, given that Gravatar images are not too large. However, imagine if the size of the contents we are proxying were thousands of megabytes in size. A much better approach would have been this:

http.createServer()
.on('request', function(req, res) {
	var email = req.url.substr(req.url.lastIndexOf('/')+1)
	if(!email) {
		res.writeHead(404)
		return res.end()
	}

	http.get('http://www.gravatar.com/avatar/'+crypto.createHash('md5').update(email).digest('hex'), function(resp) {
		resp.pipe(res)
	})
})
.listen(8080)

Here, we fetch the image and simply pipe the response to the client. At no point do we need to read the entire content into a buffer before serving it.

Mistake #9: Using Console.log for Debugging Purposes

In Node.js, “console.log” allows you to print almost anything to the console. Pass an object to it and it will print it as a JavaScript object literal. It accepts any arbitrary number of arguments and prints them all neatly space-separated. There are a number of reasons why a developer may feel tempted to use this to debug his code; however, it is strongly recommended that you avoid “console.log” in real code. You should avoid writing “console.log” all over the code to debug it and then commenting them out when they are no longer needed. Instead, use one of the amazing libraries that are built just for this, such as debug.

Packages like these provide convenient ways of enabling and disabling certain debug lines when you start the application. For example, with debug it is possible to prevent any debug lines from being printed to the terminal by not setting the DEBUG environment variable. Using it is simple:

// app.js
var debug = require(‘debug’)(‘app’)
debug(’Hello, %s!’, ‘world’)

To enable debug lines, simply run this code with the environment variable DEBUG set to “app” or “*”:

DEBUG=app node app.js

Mistake #10: Not Using Supervisor Programs

Regardless of whether your Node.js code is running in production or in your local development environment, a supervisor program monitor that can orchestrate your program is an extremely useful thing to have. One practice often recommended by developers designing and implementing modern applications recommends that your code should fail fast. If an unexpected error occurs, do not try to handle it, rather let your program crash and have a supervisor restart it in a few seconds. The benefits of supervisor programs are not just limited to restarting crashed programs. These tools allow you to restart the program on crash, as well as restart them when some files change. This makes developing Node.js programs a much more pleasant experience.

There is a plethora of supervisor programs available for Node.js. For example:

All these tools come with their pros and cons. Some of them are good for handling multiple applications on the same machine, while others are better at log management. However, if you want to get started with such a program, all of these are fair choices.

Conclusion

As you can tell, some of these Node.js problems can have devastating effects on your program. Some may be the cause of frustration while you’re trying to implement the simplest of things in Node.js. Although Node.js has made it extremely easy for newcomers to get started, it still has areas where it is just as easy to mess up. Developers from other programming languages may be able to relate to some of these issues, but these mistakes are quite common among new Node.js developers. Fortunately, they are easy to avoid. I hope this short guide will help beginners to write better code in Node.js, and to develop stable and efficient software for us all.

About the author

Mahmud Ridwan, Bangladesh
member since December 31, 2013
Mahmud is a software developer with a knack for efficiency, scalability, and stable solutions. With years of experience working with a wide range of technologies, he is still interested in exploring, encountering, and solving new and interesting programming problems. [click to continue...]
Hiring? Meet the Top 10 Freelance Node.js Developers for Hire in September 2016

Comments

Attila Györffy
Great write up. I've seen these mistakes a few times (and most probably have made the same during the early days of my node experience). In regards to Mistake #3 (Deeply nested callbacks), async.waterfall has been exceptionally helpful in writing expressive code with a clear indication towards intent. However, I'd like top point out that if those are nested in a function that takes a callback (in your example referenced by "done") and having the individual functions in `async.waterfall` with the same name is just as equally as error prone as having nested callbacks. The problem is that async.waterfall function callbacks can be easily mixed up with the parent function's callback. In order to work around a problem, I propose a convention to clearly distinguish async.waterfall function callbacks from their parent: http://eclips3.net/2015/03/20/convention-to-use-distinctive-callback-names-in-async-waterfall/ I'd love the community to pick this up as there are still may occasions that are error prone and potentially misleading to developers, even when using async.waterfall.
Bryce Ott
Thanks for the very insightful article. Particularly when it comes to developing fast, efficient, and scalable web services, I have fallen in love with Node.js. It's speed and ability to essentially run many aspects of your logic "in-memory" have proven incredibly valuable on several projects. I would also like to throw in a #11 to your list, which I would title "Underutilizing or misunderstanding NPM". One of the most valuable aspects of Node.js to developers IMO is the incredibly powerful Node Package Manager (NPM), which has essentially made it super easy for developers to both access and share Node specific libraries. Before I write any new business logic, I always check for something suitable in NPM, and have seldom been disappointed at finding at least a piece of what I needed. The pitfall however, comes when its time to share or deploy code that utilized NPM modules. Without a proper understanding of module versioning and how that is defined in the the project's package.json file, I have seen many developers get bitten (and have been myself), by critical differences and dependancies between module versions. They will work fine during development, but when deployed to another system, things break unexplainably because NPM is populating from different versions than expected. There is a lot here, but this is an excellent resource for understanding the package.json file and what goes on inside it. Each NPM module you include will have one, so if you're like me and prefer examples, take a look at several to get a better feel for how they are used: https://docs.npmjs.com/files/package.json
Ulises Ramirez-Roche
Don't use Try-Catch in your node apps! That's one of the main reasons why frameworks like Koa came about, which uses generators to gain sane error handling. Anyway, lots of technical issues and FUD with this blog post, aside from that.
mayhap
Async.auto FTW. Time to retire that old Async.waterfall ...
Mahmud Ridwan
Agreed! (Somewhat)
Mahmud Ridwan
Thank you for reading it. Also, thanks for sharing this thing about NPM. Now I wish I could make this a top 11 list!
Mahmud Ridwan
I am glad you liked it. That is some good advice about naming callbacks. Thank you for sharing.
mayhap
somewhat? it's consistent interface be it parallel or serial is a win right there ;) And the way to pass the data between the calls is very nice.
Mahmud Ridwan
Absolutely! No doubt there. I just prefer to use `async.waterfall`, when my `async.auto` starts to look like this: https://gist.github.com/hjr265/c0494b1c0446209d6b4f .. Might be a personal thing :) Although, to be fair, `async.auto` covers most use cases.
mayhap
http://pastie.org/10056791 here you go
viki53
Or Promises. Cleaner and future-proof.
Aesop Wolf
Can you clarify why we shouldn't use Try-Catch?
Ulises Ramirez-Roche
Look up Co and Koa, and some of its corresponding blog posts. But basically, native error handling in Node doesn't work very well, and thus the need for promises and generators nowadays.
Aesop Wolf
It looks like koa is telling me to USE try-catch https://github.com/koajs/koa/wiki/Error-Handling
Ulises Ramirez-Roche
"Thanks to co, error handling is much easier in koa. You can finally use try/catch!". It's in the first paragraph. Are you serious about needing help?
Ulises Ramirez-Roche
Check out this if you still have questions. https://strongloop.com/strongblog/comparing-node-js-promises-trycatch-zone-js-angular/
Tracker1
Promises still tend to have more overhead than Async and callback based methods. Just the same, I tend to prefer them... it's just worth knowing about if you've got some heavy structures. To the Author: #12 is being aware of the memory/handle limits in node... when you bump up on these limits, your process will crash. It's relatively easy to hit if you are processing millions of records from a database as a result set, for example... or not using streams with XML or CSV file handling. In many such cases it's often worthwhile to expose the GC option to call garbage collection manually after every N passes to keep your memory footprint low on utility scripts.
Matt Hamann
When dealing with "callback hell," I would assert that Promises are the solution. They provide a much more readable way of writing asynchronous code, and also provide some excellent mechanisms for dealing with errors. I would go as far as to say that with Promise libraries like "bluebird," there is actually no reason to ever use the "async" library. I've found that async often produces code as unreadable as the nested callbacks it replaced.
Mahmud Ridwan
Thanks! That mistake does sound like a good thing for people to know about. Node.js, like many other garbage collected language, does tend to get overwhelmed when too many short lived objects are instantiated.
andreastrantidis
Great article. Very well written
allrollingwolf
Are you serious about not realizing that you are blatantly contradicting yourself? You: "don't use try catch! look up koa!" Koa: "...use try catch!"
Ulises Ramirez-Roche
See, this is why I hate hanging out in these forums dude. I didn't really have the time to give a whole detailed explanation, but it's the weekend now so whatever. The point is that you must use something like Koa to gain sane error handling in Node. Note the use of the word "finally". Koa is saying that you can "finally" use Try-Catch, something that wasn't possible in Vanilla Node. It does so by the use of ES6 generators, which you get by opting into Harmony. Error handling in Node has always been sort of a dark spot, and there have been other attempts at fixing them. Like Domains. See this blog post for example: https://engineering.gosquared.com/error-handling-using-domains-node-js That being said, I tend to prefer Promises. But to each their own. Keep using Try Catch if you really want to.
Mahmud Ridwan
Thank you.
JD Ballard
#5 is innocuous; module.exports and exports are the same thing - they start as an empty object.
Mahmud Ridwan
They both start as an empty object, the same object. You are right about that part. https://github.com/strongloop/express/blob/bbcc1e1f52cb3706660d88bc2884057d71ae1f92/lib/express.js#L17 Notice how they do `exports = module.exports = createApplication;`? There is a specific reason why doing `exports = createApplication;` wouldn't quite work.
JD Ballard
Try catch is synchronous.
Francois Ward
Async is good for stuff like throttling, which Bluebird is weak at. Though promises alone don't go far enough. Bluebird.coroutine along with generators, or ES7 async/wait is where things get real.
Masudul Haque
Look at the title of this article; "Top 10 Common Node.js Developer Mistakes". First of all, these are the TOP 10 mistakes a Node.js developer can do. Secondly these are the top 10 COMMON mistakes a Node.js developer can do. After narrowing down twice, some similarities can be found but you should not claim that he copied from your article. And it was ridiculous when you said that "as well as the title".
Adrian Oprea
@saadixl:disqus don't get so heated up, it's not like @alessioalex:disqus 's blog post is private and nobody has the right to form an opinion based on it. I guess that the bottom line is that even if you use something that inspires you to write a blog article, you should quote the original source. My point is that maybe if @hjr265:disqus got inspired by that article, or any other article, maybe he could add some credits at the bottom of the post, similar to what smashingmagazine does. Cheers!
Jordan Kasper
No mention of StrongLoop's new process manager? :) https://github.com/strongloop/strong-pm Otherwise, nice article... I would add one other common mistake though: executing a callback for an otherwise async method in a synchronous fashion. For example: <pre><code class="javascript">function foo(cb) { if (!someAuditCondition) { return cb(new Error(...)); // <- This is NOT asynchronous, but the rest of the function is! } doSomeAsynAction(function(err, data) { if (err) { return cb(err); } cb(data); }); } </code></pre> One better way to do this is: <pre><code class="javascript">function foo(cb) { if (!someAuditCondition) { return process.nextTick(function() { cb(new Error(...); }); } ... } </code></pre>
Mahmud Ridwan
Thanks for the feedback. "strong-pm" looks pretty good! I will certainly take a better look at it. Regarding what you said about the callback being invoked synchronously. I can see where this can be a good thing. For example, I feel, we can think of situations where not considering this could lead to weird situations and even "Maximum call stack size exceeded" error.
Masudul Haque
According to you, if @Mahmud Ridwan got inspired by that article, or any other article, may be he could add some credits at the bottom of the post. I agree with you. But as he didn't mention any credits, that means this article may not have been inspired by another article. And if you think providing NULL credit is a must when you haven't inspired from another article, I don't see any credits in @alessio alex 's blog post. As he didn't invent Node.js, shouldn't somebody claim that somethings from his article were copied from somewhere?
Ionuț Staicu
The very fact that the author of this post deleted accusatory comments should make you think a bit...
Badru
Thanks for sharing
bendwarn
Math.pow(2, 53) | 1 should equal to 1. At least I confirmed this in chrome. The operation should behave like 0 | 1, right?
Mahmud Ridwan
Thank you for pointing that out.
Mahmud Ridwan
Thank you for reading it..
kurakar
Thank you for the article. I have one doubt regarding point #1 if nodejs performance on CPU bound task are worse, what are the solutions. Can we use a python code to perform such operations. Will it be efficient then?
Mahmud Ridwan
The first point is not quite about avoiding Node.js for CPU bound tasks. You can use Node.js for CPU intensive tasks. What you should avoid however is running CPU intensive tasks in the same Node instance/process that serves user requests. The exact solution to this depends on what exactly you are trying to achieve. For example, in some cases, you can have worker nodes that can be connected to some task queues. In other cases, you can actually use some other languages to do these deferred works.
jennifermorrison
Really its a informative article. I am sure that i will take some points from this article. Nowadays most of the people try to learn more about Node.js and AngularJs projects. Its really amazing!.Day by day node.js growth is increasing rapidly. Recently i studied one of the article regarding Node.JS. I find quality content in that topic. its really impressed me as like as your article.http://agriyaservices.blogspot.in/2015/06/create-real-time-applications-with.html
Mojotaker
Thanks for this.
shumana
Very informative. From your profile, got to know that you have worked on WebRTC. Need some help on that. If you have any tutorial or blog on that, can you provide the link?
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant technologies
About the author
Mahmud Ridwan
Python Developer
Mahmud is a software developer with a knack for efficiency, scalability, and stable solutions. With years of experience working with a wide range of technologies, he is still interested in exploring, encountering, and solving new and interesting programming problems.