Optimize Your Nest.js App Performance With These Practices
Tags: nestjs, webdev, javascript, node
Published: 2023-09-27 05:57:00

In the modern era of rapid technological advancement, users tend to lose interest in using an application if it doesn’t load quickly. Since there are multiple alternatives available for each app, users are more likely to switch to another app if you don’t optimize your app.

A survey shows that if an app takes longer than three seconds to load, the probability of losing users increases by 40%. The optimization of an app is an essential factor that plays a critical role in the app’s success.

Nest.JS applications come equipped with a wide range of tools that developers can leverage. In this article, we will be exploring ways in which you can enhance your Nest.JS app’s performance by optimizing it effectively. 

What is Nest.JS?
Nest.JS ranks among the popular JavaScript frameworks in 2023 that offer a robust set of tools for creating server-side applications, and native support for TypeScript elevates the development process to a higher level. 

You can use Nest JS to develop sophisticated, high-performing, and scalable server-side applications. It provides you with a modular structure which makes it easier to organize the code. Additionally, it comes equipped with a range of tools and plugins that you can use to optimize your app.  However, the overall performance of the application ultimately depends on the level of effort put in by the developer.

Here are some practices that can help you optimize your NestJS application to improve its performance and efficiency.

1. Implement Caching
Caching refers to the process of temporarily storing frequently accessed data in high-speed storage — cache memory. By activating caching, you can reduce the data retrieval time.  Here’s how you can implement caching in your application.

Install Cache-Manager: NestJS provides a solution for implementing caching using cache-manager. To install it, run the following command: 

```
$ npm install @nestjs/cache-manager cache-manager

```
This cache manager offers a unified interface that supports various cache storage providers beyond just the default in-memory option.

To enable in-memory caching, run the following command: 

```
import { Module } from '@nestjs/common';

import { CacheModule } from '@nestjs/cache-manager';

import { AppController } from './app.controller';

@Module({

  imports: [CacheModule.register()],

  controllers: [AppController],

})

export class AppModule {}
```

To learn more about caching, you can explore additional resources and consider switching to alternative service providers.

2. Choose Appropriate Dependency Injection Library
Nest.JS provides developers with the sweet nectar of dependency injection. This design pattern allows for a more maintainable codebase by injecting dependencies as needed. But this blessing can also turn sour if not used properly, leading to sluggish application performance.

NestJS provides built-in support for dependency injection (DI), but it also allows you to use other DI libraries if you prefer. There are several popular DI libraries that are compatible with NestJS, including:

InversifyJS: InversifyJS is a powerful and flexible DI library that supports advanced features such as constructor parameter injection and named dependencies. It has a large community and well-documented resources for developers. You can use it with NestJS by installing the `@nestjs/inject` package and importing the `InversifyAdapter` from it.

Awilix: Awilix is a lightweight DI library that emphasizes simplicity and ease of use. It has a simple API and supports many of the same features as other DI libraries, such as constructor injection and property injection. You can use Awilix with NestJS by installing the `awilix` package and using the `awilix-express` integration library.

TypeDI: TypeDI provides a simple and intuitive API for defining and injecting dependencies. You can use TypeDI with NestJS by installing the `typedi` package and importing the `TypeDIAdapter` from the `@nestjs-typeorm` package.

tsyringe: tsyringe is a lightweight and flexible DI library that supports many advanced features such as circular dependencies and conditional injection. It has a simple and intuitive API, and you can find comprehensive documentation to work with the tsyringe. To use tsyringe with NestJS, you can install the `tsyringe` package and importing the `TsyringeAdapter` from the `@nestjs-modules/injection` package.

If you’re just getting started with NestJS and don’t have any particular requirements, the built-in DI system is a good choice as it is lightweight and easy to use.

3. Use Compression
The more code you have, the slower application will become. Therefore to prevent your app performance from getting bogged down by heavy code, you can use various compression techniques available to reduce the size of HTTP responses, JavaScript, and CSS files. 

This technique includes Gzip, deflate, and Brotli compression. These tools or techniques can help you reduce the weight of your code, which will result in improved performance.

To use compression in NestJS, you can take the following steps:

1. Install the compression package:

```
npm install --save compression

```
2. Import the CompressionMiddleware from compression:

```
import * as compression from 'compression';

import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

async function bootstrap() {

  const app = await NestFactory.create(AppModule);

  // Use the compression middleware

  app.use(compression());

 

  await app.listen(3000);

}

bootstrap();
```

This file imports the compression middleware and adds it to the application using the app.use() method.

By default, the compression() middleware will use the gzip compression algorithm. You can also pass in options to customize the behavior of the middleware, such as the threshold at which responses will be compressed, using the following syntax:

```
app.use(compression({

threshold: 512 // set the threshold to 512 bytes

}));
```

With compression enabled, responses from your Nest.JS application will be compressed before they are sent to the client, reducing the amount of data that needs to be transferred over the network and improving performance.

4. Optimize database queries
You can enhance your app’s performance by optimizing your database queries. One effective method is to minimize the number of queries by utilizing query batching and compatible ORM frameworks that enable streaming and enhance the overall database process. Rather than retrieving all available data, reducing the amount of data fetched can also minimize the workload on the database and optimize its operations.

5. Revamp your Algorithm 
To optimize an app’s performance, it’s essential to fine-tune its algorithm, which entails creating a series of instructions that direct the code on how to operate. 

The algorithm’s defined steps have a direct impact on the app’s performance, as they dictate the optimal times and conditions for the code to perform specific tasks. 

Evaluating the algorithm’s optimization level is critical to enhancing the app’s overall performance, as it informs the code when to execute particular tasks and when not to, enabling it to work more efficiently. Essentially, the algorithm acts as a roadmap for the code, guiding it on how to function for optimal efficiency.

6. Leverage Web Workers
When executed efficiently, multitasking can enhance the performance of an application by allowing for faster execution of code. One way to achieve this in JavaScript is through the use of web workers, which enable multitasking and can run without interfering with the main event loop.  This approach can be particularly beneficial for resource-intensive tasks that do not require user interaction.

Web Workers are a way to run scripts in a separate thread from the main thread of a web application. Nest.JS provides built-in support for Web Workers through the worker_threads module that comes with Node.js. You can use this module to create and manage worker threads in your NestJS application.

Here is an example of how to use Web Workers in NestJS:

1. Create a new worker file:

```
// worker.js

const { parentPort } = require('worker_threads');

parentPort.on('message', (message) => {

  console.log(`Worker thread received message: ${message}`);

  parentPort.postMessage(`Worker thread received message: ${message}`);

});
```

This file creates a worker thread that listens for messages sent from the main thread, logs the message to the console, and then sends a response back to the main thread.

2. In your NestJS controller, create a new worker thread:

```
// app.controller.ts

import { Controller, Get } from '@nestjs/common';

import { Worker } from 'worker_threads';

@Controller()

export class AppController {

  @Get()

  async getHello(): Promise<string> {

    return new Promise((resolve) => {

      const worker = new Worker('./worker.js');

      worker.on('message', (message) => {

        console.log(`Main thread received message: ${message}`);

        resolve(message);

      });

      worker.postMessage('Hello from main thread!');

    });

  }

}
```

This code creates a new worker thread and sends a message to it. When the worker thread receives the message, it sends a response back to the main thread, which resolves the promise and returns the response to the client.

Note that the worker thread is created within a promise to make it easier to handle the asynchronous communication between the threads.

3. Start the application:

```
npm run start

```
This will start the NestJS application and create a new worker thread to the root endpoint. The worker thread will receive the message, log it to the console, and send a response back to the main thread, which will then return the response to the client.

Web workers can be a powerful tool for improving the performance and scalability of web applications, especially for long-running or computationally-intensive tasks. By using Web Workers in Nest.JS, you can take advantage of this technology to build faster, more efficient applications.

7. Implement Load balancing
Load balancing is a technique of evenly distributing incoming network traffic among multiple servers to prevent any single server from becoming overloaded and causing the application to become unresponsive and sluggish. 

NestJS provides a built-in module, “@nestjs/platform-socket.io”, for Socket.IO-based applications. Additionally, you can use third-party load balancers like NGINX or cloud-based balancers such as WS Elastic Load Balancer or Google Cloud Load Balancer to accomplish this task.

Here’s an example of how to implement reverse proxy load balancing using ExpressJS in NestJS:

1. Install the @nestjs/platform-express package and http-proxy-middleware package:

```
npm install --save @nestjs/platform-express http-proxy-middleware

```
2. Import ExpressAdapter from @nestjs/platform-express:

```
import { ExpressAdapter } from '@nestjs/platform-express';

```
3. Create a new main.ts file that initializes the NestJS 

```
application using the ExpressAdapter:
import { NestFactory } from '@nestjs/core';

import { ExpressAdapter } from '@nestjs/platform-express';

import { AppModule } from './app.module';

import * as express from 'express';

import { createProxyMiddleware } from 'http-proxy-middleware';

 

async function bootstrap() {

  const server = express();

  const app = await NestFactory.create(

    AppModule,

    new ExpressAdapter(server)

  );

 

  const servers = [

    { target: '<http://localhost:3001>' },

    { target: '<http://localhost:3002>' }

  ];

 

  servers.forEach(server => {

    server.use('/api', createProxyMiddleware({

      target: server.target,

      changeOrigin: true,

      pathRewrite: { '^/api': '' }

    }));

  });

 

  await app.listen(3000);

}

bootstrap();
```

This file creates an ExpressJS application using the ExpressAdapter from @nestjs/platform-express.
The createProxyMiddleware function from http-proxy-middleware is used to create a reverse proxy for each upstream server. The changeOrigin option is set to true to ensure that the origin header of the incoming request is modified to match the target server. The pathRewrite option is used to remove the /api prefix from the incoming requests before forwarding them to the upstream server.

The reverse proxy is mounted on the /api path, so any incoming requests to /api will be forwarded to one of the upstream servers.

4. Start the reverse proxy server:

```
node server.js

```
This will start the reverse proxy server on port 3000.

5. Start the upstream servers:

```
node server1.js

node server2.js
```

This will start two upstream servers on ports 3001 and 3002.

That’s it! You now have a reverse proxy load balancer that will distribute incoming traffic across multiple servers using ExpressJS in Nest.JS.

8. Enable Lazy Loading
Lazy loading is a technique that delays the initialization of objects and loads only the necessary components to speed up the application’s loading time. Rather than loading all objects at once, it allows the application to load the necessary data.

To enable lazy loading in Nest.JS, you need to use the loadChildren property in the @Module decorator. Here’s an example:

```
import { Module } from '@nestjs/common';

@Module({

  imports: [

    // ...

    {

      path: 'users',

      loadChildren: () => import('./users/users.module').then(m => m.UsersModule),

    },

  ],

})

export class AppModule {}
```

In this example, the loadChildren property is used to import the UsersModule lazily when the users path is accessed. The loadChildren property takes a function that returns a promise that resolves to the module to be loaded. In this case, we use the import() function to load the UsersModule dynamically.

You can also use the canLoad method in the route guard to control the access of loading the module. By default, the canLoad method returns true, but you can implement your own logic to determine whether the module should be loaded or not.

```
import { CanLoad, Route, UrlSegment } from '@nestjs/common';

@Injectable()

export class AuthGuard implements CanLoad {

  canLoad(route: Route, segments: UrlSegment[]): boolean {

    // Implement your logic here

    return true;

  }

}

Then you can use this guard in the @Module decorator.

import { Module } from '@nestjs/common';

import { AuthGuard } from './guards/auth.guard';

@Module({

  imports: [

    // ...

    {

      path: 'users',

      canActivate: [AuthGuard],

      canLoad: [AuthGuard],

      loadChildren: () => import('./users/users.module').then(m => m.UsersModule),

    },

  ],

})

export class AppModule {}
```

In this example, the AuthGuard is used as both a canActivate and canLoad guard. It ensures that the user is authenticated before loading the module.

Conclusion
This article explores various effective techniques to enhance the performance of Nest.JS applications, which can be beneficial for developers at all levels. 

We have highlighted some commonly used best practices that can make your app more well-rounded. If you have an interest in learning more about NestJS development or require assistance in developing an app, please feel free to reach out to us.

At Brilworks, we offer dedicated or full-time NodeJS developers who specialize in NestJS-based application services. Our experienced team can efficiently develop and publish a well-optimized, industry-level application tailored to your specific requirements. If you are in need of these services, please do not hesitate to contact us.
Source: [https://www.brilworks.com/blog/optimize-your-nest-js-app-performance-with-these-practices/](https://www.brilworks.com/blog/optimize-your-nest-js-app-performance-with-these-practices/)