How to use dependency injection in NodeJS TypeScript projects with Inversify

How to use dependency injection in NodeJS TypeScript projects with Inversify

Dependency injection (DI abbreviated) is a technique that removes internal dependencies from the implementation by enabling these dependencies to be injected externally. All of this is a subset of Inversion of Control Principle (IoC abbreviated). This popular principle is based on an idea that objects should not create objects on which they depend to perform some activity. In other words high level modules in an application shouldn't depend on the low level modules; both should rather depend on abstractions.

Let's imagine you have an application that retrieves some data from a MongoDB instance, you shouldn't have some code like this:

Não foi fornecido texto alternativo para esta imagem

Although it's a totally working code, I can't even count the total amount of anti-pattern and architectural errors in this snippet. The part I would like to show you to demonstrate the pattern is listCars method.

The listCars method is directly coupled to database implementation, in our case, mongodb client. What would happen if for any reason the team needed to change from mongodb to other non-relational database, or even for a relational database like postgres, moreover for a non-database style like flatfiles in disk? (are there use cases for it? 😅)

The answer: re-work and headache.

"But I can make it work again in 5 minutes of refactoring" - Some Internal Dev

In fact, making those suggested changes appears to be trivial and fast to do, but remember, this is a fictional scenario and there is only 1 method. Imagine a hundred of methods in dozens of classes. Believe me when I say: headache 😵

The real solution: Dependency Injection

Our code meets Inversify

Inversify is powerful and lightweight inversion of control container for JavaScript & NodeJS. It implements IoC and allow us to inject dependencies, uncoupling our method from any implementation details.

Using inversify, we can switch between any implementation with zero impact in the code. No refactoring, no breaking changes.

Above I wrote an database example but it could be anything writable in code. External sources, Databases, Usecases, Controllers, Frameworks etc. Everything can (should) be switched with zero (or minimum) impact in the current code.

The first thing you need to do is to define an interface to be used as an abstraction of the implementation:

Não foi fornecido texto alternativo para esta imagem

Then, you create an concrete implementation of this contract. As can be seen below, there is extra configurations in the class.

Não foi fornecido texto alternativo para esta imagem

The @injectable is a decorator available in the Inversify library, it allows you to indicate the current class can be "injected" using the container.

The next thing to do is to write an unique identifier to this specific injection, it can be made using a plain old Javascript Object.

Não foi fornecido texto alternativo para esta imagem

Don't worry about Symbols, it's just a native way of JS to create unique identifiers. According to MDN Web Docs:

Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for("key") call will always return the same Symbol for a given value of "key".

The last step is to glue everything up using the .bind() method from Inversify container:

Não foi fornecido texto alternativo para esta imagem

A simple manner to understand the bind method above is to think straightforward: "Everytime I invoke this Interface, given this specific Locator, I'll get this specific implementation".

Now we are ready to go! Let's use this container and inject a new repository instance in our Car class.

Não foi fornecido texto alternativo para esta imagem

There is an interesting point here. We inject a repository using Car constructor method. Notice that Car class doesn't know anything about repository implementation but only its interface. This allows us to change the implementation with no impact in the Car class.

For example, we can stop using MongoDB and change to FlatFiles instead:

Não foi fornecido texto alternativo para esta imagem

The only part to put your hands on is the container:

Não foi fornecido texto alternativo para esta imagem

With this mininum change we switched from MongoDB to other storage schema without no impact in the main logic (Car class).

Conclusion

Using Inversify to inject dependencies and help us to following the IoC principle is a great way to keep our code less coupled and cleaner. We saw that we can switch from any implementation without impact the main logic which are receiving the specified abstraction.


Thank you for reading until the end. <3

Sayali Rahane

Software Engineer @Centralogic | Beta MLSA 🛡️ | EX- GDSC Lead’ 23 | Nodejs | ASP.Net | Devops | Backend Dev | Leetcode 300+

9mo

Really Helpful!!

Like
Reply
Thiago Trindade

Software Developer | Node.js | Typescript | AWS | Express | MySQL

4y

Great article! Exemplifies very well how to use DI

To view or add a comment, sign in

More articles by Andrew Ribeiro

Others also viewed

Explore content categories