NestJS 8 vs NestJS 9

NestJS 8 vs NestJS 9

Who doesn't love new toys? But what features does this new upgrade have for us and is it beneficial to migrate? That's what this blog is about.

Before we move to the comparison between NestJS 8 vs NestJS 9, let us understand what NestJS is.

What is Nest?

NestJS is a Javascript framework designed for developing applications based on Node.js architecture. It allows the developer to use TypeScript and/or Javascript to build efficient, scalable and OOP structured applications ( OOP is the way, isn't it? ), without taking away the benefits of the Functional Approach that JavaScript and its other frameworks offer.

"Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming)." - Official Documentation

Before we know what Nest is, we should know why Nest is?

In this era of rapidly progressing language frameworks and a world of extensive libraries built to facilitate development and make it a child's play, Node.js has dominated both the client and the server side world as a powerful and resourceful JavaScript framework.

A wide range of libraries available for Node.Js, make it the first go-to option for building scalable, sturdy and testable APIs. However, there is still one issue which none have been able to solve, architecture.

Nest provides an out-of-the-box application architecture which allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications. - Official Documentation

More about NestJS!

NestJS is like a strict parent that never lets you go astray from the set path. It has a definite structure that any developer follows unless he wants trouble. Well, the rules are made to make things easier later. In the beginning, it may feel a bit tedious and futile to maintain all that structure but trust me, when it saves us from long nights of work trying to fix the horrendous and haphazard code, you will be really grateful for the work you put. 

Now let us go through the structure that NestJS enforces.

Modules

A module is a class annotated with the @Module annotator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.
The Task Module

A module informs the NestJS compiler that which providers will be available in the module and which providers the module will export to be used by other modules.

All the modules which we intend to use in the application are then finally imported into the app.module.ts file that we get out of the box with NestJS.

The core of the app: app.module.ts

Controllers

In NestJS, we have loosely coupled modules, with each module having an independent task. These modules are implemented with the help of Controllers.

This is the most basic example of a controller that handles routing in a NestJS application

The controllers allow the NestJS application to separate concerns and group-related routes minimising repetitive code. For instance, the task controller in the above snippet handles all the functionality and interactions related to the 'task'. Controllers are defined with the help of the @Controller decorator. The controllers contain the routes which are described by the @Get, @Post, @Put, @Delete and @Patch decorators exposed by @nestjs/common.

The decorators take a string parameter, the route that will trigger these endpoints. For instance, @Get("/:id") is a route that will fetch a task by its ID.

Providers

Providers are a fundamental concept in Nest. Many of the basic Nest classes may be treated as a provider – services, repositories, factories, helpers, and so on. The main idea of a provider is that it can be injected as a dependency; this means objects can create various relationships with each other, and the function of "wiring up" instances of objects can largely be delegated to the Nest runtime system.

Providers are the NestJS' way of providing reusable code that exposes a particular functionality that other modules can use. They can be services that are Injectible classes that will be available in the Module they are injected in as instance members. This facilitates abstraction and code reusability.

?
Providers are classes that are annotated with @Injectible decorators.
A basic service provider

NestJS 8 vs NestJS 9

Finally, we come to the part we have been waiting for the most, NestJS 9 and what it has in store for us.

Fancy New Features

REPL (read–eval–print loop)

REPL is a simple interactive environment that takes single user inputs, executes them, and returns the result to the user. The REPL feature lets you inspect your dependency graph and call methods on your providers (and controllers) directly from your terminal.

REPL is a kind-of a terminal which allows us to derive the output of a single statement of code without writing an entire file and running it. It helps in unit testing of modules and their services to evaluate their output against set test cases. It can be seen as a kind of test-drive to a new car.

To be able to run your NestJS application in REPL mode, you will have to add a repl.ts file to your project alongside main.ts file.

repl.ts file

Now in the terminal, you must type following spell -

$ npm run start -- --entryFile repl
## OR yarn start --entryFile repl

Once it runs successfully, you will be able to see the following screen -

Once it's up, you can now use the REPL to call the modules and the services using built-in methods like get(). For instance, you can get the AppService provider and call its getHello() method and see its output/response on the terminal itself.

You can also execute any Javascript code to perform any required task, such as executing an asynchronous method -

You can either get a list of methods of a controller using methods() function or get all the registered modules as a list together with their controllers and providers using the debug() method -

Optimisation in Providers

In NestJS, providers are the ways to implement the business logic of the application. To optimise the application and to suit business needs, a provider has a scope within which it can be accessed by requests from a customer. A provider has 3 kinds of scope in NestJS -

DEFAULTA single instance of the provider is shared across the entire application. The instance lifetime is tied directly to the application lifecycle. Once the application has bootstrapped, all singleton providers have been instantiated. Singleton scope is used by default.
REQUESTA new instance of the provider is created exclusively for each incoming request. The instance is garbage-collected after the request has completed processing.
TRANSIENTTransient providers are not shared across consumers. Each consumer that injects a transient provider will receive a new, dedicated instance.

You can specify a provider to be of request scope by specifying it in the parentheses of the @Injectible decorator -

A Request Scoped Provider

In a request-scoped provider as explained above, a new instance is created for each request which is later collected by the garbage collector. Request scope is bubbled upwards, i.e. a controller having even a single request scoped provider will itself be a request scoped controller, which means it'll have to be collected by a garbage collector.

The problem with this approach is the number of latent requests and increased resource utilisation and thereby exhaustion. Suppose if there are 100k parallel requests, there will be 100k short-lived parallel instances of the controller.

A common scenario where a database is involved in a provider in which we want to separate scopes for different customers accessing these databases. In that case we declare the database provider as request-scoped. Consequently, all providers using this provider will in turn be request-scoped and the effects will be evident in the application's performance.

The Solution?

If your providers don't rely on any property that's truly unique for each consecutive request (e.g., request UUID) but instead there are some specific attributes that let us aggregate them, then we have no reason to recreate the DI sub-tree on every incoming request.
This is exactly where durable providers come in handy!

Herein, NestJS allows us to create a strategy to differentiate multiple requests and use that strategy to decide whether to make a new instance for a new request.

Creating a Strategy To Differentiate Requests

After this strategy is in place, we have to register it somewhere in the code, like in the main.ts file.

ContextIdFactory.apply(new AggregateByTenantContextIdStrategy());

Lastly, to turn a regular provider to a durable provider, set the durable flag to true.

Durable Provider
As long as the registration occurs before any request hits your application, everything will work as intended.

Redis transporter

In v9, the Redis transport strategy will no longer use redis package under the hood but instead was migrated to leverage ioredis.

Apart from these NestJS 9 also brings support for Fastify v4 and has dropped support for Node.js v10, so make sure to use at least the latest LTS version.

Conclusion

NestJS 9 has brought in many improvements over NestJS 8. Features like REPL will make evaluating dependencies and testing modules easier and faster. Durability in providers will solve the problem of request scoped controllers and their effect on performance of the app.