API with NestJS #148. Understanding the injection scopes

JavaScript NestJS

This entry is part 148 of 148 in the API with NestJS

When a NestJS application starts, it creates instances of various classes, such as controllers and services. By default, NestJS treats those classes as singletons, where a particular class has only one instance. NestJS then shares the single instance of each provider across the entire application’s lifetime, creating a singleton provider scope.

If you want to know more about the Singleton pattern, check out JavaScript design patterns #1. Singleton and the Module

The singleton scope fits most use cases. Thanks to creating just one instance of each provider and sharing it with all consumers, NestJS can cache them and increase performance. However, in some cases, we might want to change the default behavior.

The request scope

To change the default provider scope, we must provide the property to the decorator.

logged-in-user.service.ts

Thanks to adding , our is initialized every time a user makes an HTTP request handled by a controller that uses this service.

Using the request object

The object contains information about the HTTP request made to our API. Since our service is request-scoped, we can access the object.

logged-in-user.service.ts

It’s very common to implement authentication using JSON Web Tokens using the Passport library. With this approach, the information about the logged-in user is attached to the object.

If you want to know more about authentication with NestJS, check out API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies

To access the information about the user, we should create an interface that extends the interface imported from the Express library.

request-with-user.interface.ts

Using the type from Prisma we can tell TypeScript that property includes the fetched through a relationship.

We’ve used this interface in other parts of our application before.

articles.controller.ts

In the above case, the property is always defined thanks to using the . If we want our to handle a situation when the user is not logged in, we can modify the and make the property optional. One way to do that would be to use the type-fest library.

logged-in-user.service.ts

We can now use our service in a controller, for example.

articles.controller.ts

We need to remember, though, that the property will not be defined if we don’t use the , which requires the user to be logged in.

The scope hierarchy

A significant downside to having a request-scoped provider is that a controller that depends on a request-scoped provider is also request-scoped. Therefore, the is request-scoped because it uses the , which is request-scoped.

Using request-scoped providers will affect our application’s performance. Even though NestJS relies on cache under the hood, it still has to create an instance of each request-scoped provider. Therefore, we should use request-scoped providers sparingly if performance is one of our priorities.

The transient scope

When implementing logging, it’s a good practice to create a separate instance of the class for each of our services. This way, we can provide the context to the constructor.

articles.service.ts

Thanks to this approach, we see that a particular log comes from the .

If you want to know more about logging in NestJS, check out API with NestJS #113. Logging with Prisma or API with NestJS #50. Introduction to logging with the built-in logger and TypeORM

With the default singleton scope, a single provider instance is shared across the entire application. However, with the transient scope, each provider receives a dedicated instance. We can use this to create a smart logger service.

logger.service.ts

Thanks to using , we can access the parent class that used our . Because of that, we no longer need to manually provide the name of each class every time we want to use the logger.

articles.service.ts

NestJS creates an instance of the specifically for the . The is aware that it was created specifically for the and creates a new instance of the .

Summary

In this article, we’ve discussed injection scopes and their use. With the request scope, we can create services and controllers that are reinitialized for each request. This can come in handy when we want to react to request headers, including the JSON Web Token. However, to avoid performance issues, we need to avoid overusing request-scoped providers.

With transient-scoped providers, we can create a dedicated instance for each consumer. This can be useful when we want to configure our service differently for various consumers. To understand that, we created a logger service that is configured based on the provider that imports it.

The knowledge of various injection scopes can definitely come in handy when dealing with various more advanced use cases in NestJS.

Series Navigation<< API with NestJS #147. The data types to store money with PostgreSQL and Prisma
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Esteban
Esteban
1 month ago

OMG! This is pure gold! Thank you very much for this information!