DEV Community

Cover image for 3 Ways to Optimize Your Node.js Application Performance
Pavan Belagatti
Pavan Belagatti

Posted on

3 Ways to Optimize Your Node.js Application Performance

Node.js is a popular runtime environment for building fast, scalable, and efficient web applications. However, even with its inherent strengths, poorly optimized Node.js applications can suffer from performance bottlenecks, resulting in slow load times, unresponsive servers, and increased operating costs. To maximize the potential of your Node.js app and ensure that it can handle high levels of traffic without faltering, it's essential to optimize its performance. In this tutorial, we will explore some effective techniques for optimizing your Node.js app's performance, from analyzing performance metrics to implementing caching strategies and utilizing other best practices. By the end of this tutorial, you will have a comprehensive understanding of how to improve your Node.js app's performance and deliver a superior user experience.

Prerequisites

Tutorial

Our Node.js application is here - https://github.com/pavanbelagatti/Simple-Node-App

You can see the app.js in the root of the application's repo,

const express = require('express');

const app = express();
const PORT = process.env.PORT || 3002;

app.get('/', (request, response) => {
  response.status(200).json({
    message: 'Hello Docker!',
  });
});

app.listen(PORT, () => {
  console.log(`Server is up on localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

You can run the application by using the following command,

node app.js
Enter fullscreen mode Exit fullscreen mode

Now, let's talk about optimizing the performance of this application.
Here are some techniques you can use:

Caching:

Caching can help reduce the amount of time it takes for your application to respond to requests. In Node.js, you can use the node-cache package to implement caching.

Use asynchronous code:

In Node.js, asynchronous code can help improve the performance of your application.

Use cluster module:

The cluster module can help improve the performance of your Node.js application by allowing it to run on multiple cores.

Let’s add caching, asynchronous code and cluster module techniques to improve the performance of our Node.js application.

Here is the updated app.js file

const express = require('express');
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const NodeCache = require( "node-cache" );

const app = express();
const cache = new NodeCache();
const PORT = process.env.PORT || 3002;

// Define a function to handle the request
const handleRequest = (request, response) => {
  // Check if the request is already cached
  const key = request.url;
  const cachedResponse = cache.get(key);
  if (cachedResponse) {
    // If the response is cached, return it
    console.log(`Serving from cache: ${key}`);
    return response.status(200).json(cachedResponse);
  }

  // If the response is not cached, perform the operation asynchronously
  console.log(`Processing request: ${key}`);
  setTimeout(() => {
    const responseData = {
      message: 'Hello Docker!',
    };
    cache.set(key, responseData);
    console.log(`Caching response: ${key}`);
    return response.status(200).json(responseData);
  }, 1000);
};

// Use clustering to take advantage of multiple CPU cores
if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  // Listen for worker exit events
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
} else {
  // Set up the server
  app.get('/', handleRequest);

  app.listen(PORT, () => {
    console.log(`Worker ${process.pid} is up on localhost:${PORT}`);
  });
}
Enter fullscreen mode Exit fullscreen mode

The node-cache dependency is missing. Add it using the below command

npm install node-cache
Enter fullscreen mode Exit fullscreen mode

You can run the application with the same command

node app.js
Enter fullscreen mode Exit fullscreen mode

You won’t notice anything as we still have to check how the performance has been improved in the updated server.

The above code includes the following optimizations:

  • Caching using the node-cache module: We create a new instance of node-cache and use it to store responses for each request URL. When a new request comes in, we first check if the response is already cached. If it is, we return the cached response instead of generating a new response.

  • Asynchronous code: We use the setTimeout function to simulate an asynchronous operation that takes 1 second to complete. This allows the server to continue processing requests while the asynchronous operation is running.

  • Cluster module: We use the cluster module to create a worker process for each CPU core. This allows the server to handle multiple requests simultaneously and take advantage of all available CPU cores.

Let’s test the performance of the old vs. optimized server.

To test the performance of the servers under load, you can use a load testing tool such as Apache JMeter or Siege. These tools allow you to simulate a large number of concurrent users making requests to your server and measure the response time, throughput, and other performance metrics.

Here's an example of how you can use Siege to test the performance of your servers:
Install Siege: You can install Siege using the following command on Unix-based systems:

sudo apt-get install siege
Enter fullscreen mode Exit fullscreen mode

And for MAC, you can use the below command to install Siege.

brew install siege
Enter fullscreen mode Exit fullscreen mode

Once Siege is installed, you can run the following command to simulate 100 concurrent users making 1000 requests to your server:

siege -c 100 -r 1000 http://localhost:3002/
Enter fullscreen mode Exit fullscreen mode

Before this, make sure your server is up using the following command,

node app.js
Enter fullscreen mode Exit fullscreen mode

Then, run the Curl command to measure the response time

time curl http://localhost:3002/
Enter fullscreen mode Exit fullscreen mode

See the results below
3002 local

The output can be broken down into the following components:

  • 0.00s user: This is the amount of time that the CPU spent executing user code (i.e., the time spent executing your JavaScript code).
  • 0.00s system: This is the amount of time that the CPU spent executing system code (i.e., the time spent executing code in the operating system or other system-level code).
  • 39% cpu: This is the percentage of CPU usage during the execution of the code.
  • 0.018 total: This is the total time it took to execute the code (i.e., user time + system time).

Now, let’s compare the performance with the old server.
Follow the same method, just make sure that your old server (app.js) is running on your local 3001 port for this experiment.

Run the application

node app.js
Enter fullscreen mode Exit fullscreen mode

Add the load with Siege

siege -c 100 -r 1000 http://localhost:3001/
Enter fullscreen mode Exit fullscreen mode

Then, run the Curl command to measure the response time

time curl http://localhost:3001/
Enter fullscreen mode Exit fullscreen mode

See the results below,
3001 local

  • CPU spent executing code: 0.00s
  • Time spent executing system code: 0.01s
  • % of CPU usage for executing the code: 51%
  • Time taken to execute the code: 0.020

It is evident and more obvious which server is more optimized.

It is important to note that optimizing your Node.js app's performance is an ongoing process that requires continuous monitoring and tweaking. By implementing these techniques, you can significantly improve the performance of your app and ensure that it meets the needs of your users. With proper optimization, your Node.js app can handle high traffic volumes and deliver a seamless user experience, which is crucial for the success of any online business or application.

If you enjoyed this article, be sure to check out my other articles on Node.js.

Top comments (3)

Collapse
 
combo profile image
Combo

Thanks for this list. I think that while caching can always be added later as an optimization (really no need to do it advance), the async part should be part of the design, changing sync to async as an optimization in non-trivial code can be very costly.

Collapse
 
fahdw profile image
FahdW

I think one of the biggest things people will need to learn is how to bust their cache. After an update on those endpoints they need to map which keys to bust

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Redis is a must-have imho