We will explore ten expert tips for optimized performance in Node.js applications.
1. Always use asynchronous functions
Why should we always write asynchronous functions?
Because this is the best part of Node.js: asynchronous functions perform non-blocking I/O, keeping the CPU from sitting idle.
For an application that runs a lot of I/O, this simple trick makes the server work better because a server processing non-blocking I/O can handle multiple requests while one request is performing I/O. See the example:
var fs = require('fs');
// Performing a blocking I/O
var file = fs.readFileSync('/etc/passwd');
console.log(file);
// Performing a non-blocking I/O
fs.readFile('/etc/passwd', function(err, file) {
if (err) return err;
console.log(file);
});
2. Use the async module for better function organization
One challenge of working with asynchronous functions is handling multiple chained callbacks.
Many chained callbacks make the code ugly and difficult to read: a classic callback hell.
var fs = require('fs');
// A common callback hell example
fs.readFile('/etc/passwd', 'utf8'function(passwdErr, passwd) {
if (passwdErr) return passwdErr;
fs.readFile('/etc/hosts', 'utf8', function(hostsErr, hosts) {
if (hostsErr) return hostsErr;
fs.mkdir(__dirname + '/test', function(dirErr) {
if (dirErr) return dirErr;
var data = passwd + hosts;
fs.writeFile(__dirname + '/test/data', data, 'utf8',
function(err) {
console.log('Done!');
});
});
});
});
To avoid this callback hell, you can use the async module. This module has many tricks and magic for handling multiple asynchronous functions. See this example that eliminates the callback hell:
// You must install async before: npm install async
var async = require('async');
var fs = require('fs');
async.waterfall([
function(callback) {
fs.readFile('/etc/passwd', 'utf8', callback);
},
function(passwd, callback) {
fs.readFile('/etc/hosts', 'utf8', function(err, hosts) {
if (err) {
return callback(err);
}
var data = passwd + hosts;
return callback(null, data);
});
},
function(data, callback) {
fs.mkdir(__dirname + '/test', function(err) {
if (err) {
return callback(err);
}
return callback(null, data);
});
},
function(data, callback) {
fs.writeFile(__dirname + '/test/data', data, 'utf8', callback);
}
], function(err) {
if (err) {
console.log(err);
return;
}
console.log('Done!');
});
At first sight, this code looks bigger than the first one, but the async..waterfall uses an array structure to handle multiple asynchronous functions better than the traditional callback method.
Another async feature is async.parallel(), which executes asynchronous tasks in parallel.
3. Use ES6 Generators to organize asynchronous functions
Another way to avoid callback hell is by using Generators from ES6. See how the previous example will look using this alternative solution:
var fs = require('fs');
function* fileTask() {
var passwd =
yield fs.readFile('/etc/passwd', 'utf8');
var hosts =
yield fs.readFile('/etc/hosts', 'utf8');
var data = passwd + hosts;
yield fs.mkdir(__dirname + '/test');
yield fs.writeFile(__dirname + '/test/data', data, 'utf8');
}
var task = fileTask();
task.next(); // Runs the "yield fs.readFile('/etc/passwd', 'utf8');
task.next(); // Runs the "yield fs.readFile('/etc/hosts', 'utf8');
task.next(); // Runs the "yield fs.mkdir(__dirname + '/test');
task.next(); /* Runs the "yield fs.writeFile(__dirname +
'/test/data', data, 'utf8');*/
4. Use Node.js to send data
Node.js servers work better when they send data instead of an entire HTML page. This is why this platform became so popular for REST APIs.
In general, this kind of application works with JSON data, which is native to JavaScript, and since Node.js is JavaScript, there are no parsing tasks between JSON and other data formats, so there is only JSON traffic between the server and clients, which increases the server’s performance by eliminating the parsing tasks.
5. Use Nginx or Apache for static servers
Don’t waste your Node servers by letting them serve static files; servers like Nginx and Apache work better for this task. The reason is simple: Node.js is better at processing data than serving static files. Nginx and Apache have extensive configuration for serving static files and useful caching strategies. As I said before, always use Node servers for processing data.
6. Avoid cookies and sessions
Cookies and sessions store temporary state on the server. Maintaining state is expensive for servers.
Today, it’s common to build stateless APIs that use token-based authentication like JWT, OAuth, and others. These tokens are kept on the client side and prevent servers from managing state.
7. Use the cluster module for parallel processing
The cluster module is native to Node.js and creates multiple application processes, including a master cluster that acts as a load balancer to distribute requests among the slave clusters.
This technique optimizes your servers by using all CPU cores for parallel processing.
8. Enable Streaming responses
The stream module is native. It allows the streaming of large data for a specific response.
This is useful when the server needs to send videos, audio, or any type of data because streaming lets the request send data in chunks instead of all at once, which helps avoid buffer overflow on the server.
9. Always use the latest stable version
This tip is obvious, but always use the latest stable version of Node.js because improvements to the JavaScript V8 runtime often bring better memory and CPU optimization.
10. Optimize, but don’t forget to protect
Optimization is essential but not enough. You have to think about security.
At Jscrambler, we help companies optimize and protect their Node.js applications.
Sign up for our free trial, and let us know if you have any questions. We have a team of experts ready to help you!