Back to the homepage
NestJS

How to Follow the Dependency Inversion Principle in NestJS and Angular

In the NestJS documentation, we can find the following sentence:

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.

Let’s test this statement by checking if Nest allows us to easily follow one of the SOLID principles: the Dependency Inversion Principle.

Nest is highly inspired by Angular, so all recipes in this article work in Angular as well.

For a quick reminder, the principle states:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces).
  2. Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

Example

To explain how to implement DIP, let’s first consider the following example.
We create an application that will analyze data about our repositories on the GitHub platform. Our current task is to implement an endpoint that will return the number of active pull requests for a given repository.

That functionality can be quickly implemented as shown below, but it does not comply with our principle:

Even moving that HTTP call to a dedicated service is not a proper way to hide the implementation from the controller:

Implementation of the Dependency Inversion Principle

The proper implementation should look like this:

  1. We need to create an abstraction layer for getting the collection of pull requests. This abstraction is achieved by the RepositoryService abstract class.
  2. To the AppController, we are trying to inject something that is hidden by the RepositoryService injection token.
  3. The GithubInfrastructureModule says that in that place, it should be provided a GithubRepositoryService class.

OK, but why couldn’t we use an ordinary interface as an abstraction layer?

The answer is what happens with interfaces during the transpiling process from TypeScript to JavaScript. The problem is that we will lose all information about the RepositoryService interface’s existence and what should be injected to the AppController, and basically, it is not possible to pass an interface as a property value inside module providers.

The situation is completely different when it comes to classes. Even an abstract class after transpilation will be an ordinary class that can be used as an injection token. In TypeScript, we can implement a class by another class that is the same as an interface.

Why Is This So Important?

Let’s say that the requirements of our application have changed. We need to also support the repositories stored on Bitbucket as a dedicated application instance. If we hadn’t added it earlier, now we would have to add a lot of ifs in our services and controllers to prepare a proper HTTP call for the needed data.

With that elegantly hidden data source layer, we can just create a dedicated module for Bitbucket’s services and properly import our feature model as follows:

Now the GithubRepositoryService or the BitbucketRepositoryService will be injected into the AppController depending on the environment without any changes in the code of the outer layers.

Good examples of where this applies in an Angular application are when:

  • We are creating web and mobile applications and they are loading data in a different way.
  • We are building an application with SSR. While receiving data on the server side, we also want to save them in the TransferState. And on the browser side, we want to get them from it.

In both cases, following the Dependency Inversion Principle will help us implement and reduce changes and logic in high-level modules that use our data access layer. The only difference is that we basically shouldn’t depend on the environment to decide which module should be imported.

Summary

Following the SOLID Principles gives us a lot of benefits. Moreover, they make our system reusable, maintainable, scalable, testable, and more. Nest and Angular allow us to use them in an easy and elegant way.

If you are interested in how we can use GithubRepositoryService and BitbukcetRepositoryService in the same application instance, please check this repo.

After all, the statement from Nest’s documentation can be considered as a truth!

About the author

Maciej Sikorski

Maciej spends most of his time designing applications in Angular and Node.js, additionally he creates and supports libraries and other tools which he uses in his everyday work. He is heavily involved in the development of the NestJS technology and its community. He also runs a blog in English about NestJS and Angular.

Don’t miss anything! Subscribe to our newsletter. Stay up-to-date with the latest trends, tips, meetups, courses and be a part of a thriving community. The job market appreciates community members.

Leave a Reply

Your email address will not be published. Required fields are marked *