Understanding Dependency Injection with NestJs

Understanding Dependency Injection with NestJs

What is NestJs?

NestJs is a framework built on top of Node.js for building robust and scalable backend applications. It uses Express as its HTTP server framework but can be used with Fastify also. It encapsulates a lot of under-the-hood implementations of Node and Express and gives you a powerful framework with features like Object Oriented Programming, Functional Programming and Functional Reactive Programming. It follows a module-based architecture where each module can be treated as a separate feature of an application. A module is further broken into Controllers, Services and Repository.

A typical Nest module looks like this:

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService, AppRepository],
})
export class AppModule {}

Decorators, such as @Module, are used to define the behavior of a class. imports is used to import other modules within this one. Controllers are defined within controllers, and services and repositories are defined within providers. These are classes on their own whose instance is provided to the module through Dependency Injection.

But how is that done? Let's see.

What is Dependency Injection?

When you are building an application, it is more than likely that some parts of your code will depend on some other parts. Take the example of the above Nest module. Repository is the layer that talks to the database, Service contains the business logic and Controller handles the incoming HTTP request. Here, the Service will be dependent on the Repository, and the Controller will be dependent on the Service. And as all of these are classes, we will have to provide an instance of the depending class to the dependent class.

This is where Dependency Injection comes in. It is just a very fancy name for something very simple - providing someone with something it depends on for doing some work.

Dependency Injection is a design pattern for building software. It implements Inversion of Control (IoC), a design principle, which aims to make the classes as loosely coupled as possible, which means a class should only be concerned with performing its core functionalities and nothing else (for example, instantiating other classes it depends on). Imagine you hiring a maid to do all the household chores so that you can focus on your main work.

With dependency injection, the job of creating instances of classes and providing (or injecting) them wherever required is handled by an IoC container. So you will not be creating any objects and passing them along everywhere. Instead, you can just focus on writing the core functionalities of your application.

Dependency Injection in NestJs

In Nest, the dependency injection is handled by the NestJs runtime system. A class can be made a provider (which can be injected into other classes) by using the @Injectible decorator. For example, you can create a service like this:

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}
  private getAllUsers() {
    return this.userRepository.getAllUsers();
  }
}

Here, the UserService depends on UserRepository whose instance it needs in order to complete the getAllUsers method. In Nest, these are called Providers and are classes that can be injected as a dependency. These providers are added to a module in the providers array as mentioned above.

Objects in NestJs are created during runtime and are handled by its DI container. In general, independent classes (for example repositories) are instantiated first, followed by classes that depend on them. For example, if A depends on B, B will be instantiated first as A would require an instance of B during its object creation. But there can also be cases of circular dependency, wherein A depends on B and B also depends on B. NestJs provides two ways to resolve such cases - forwardRef and ModuleRef. You can read more about them here.

NestJs modules and providers (across a module) are singletons by default, which means that at any point in time, only one instance of a class exists throughout the component. Modules are application-level singleton while providers are module-level singleton.

Conclusion

NestJs is a great framework to build modern and scalable services with Node.js and TypeScript. Its structure employs many architectural features that make development strict but also robust, especially if you are working across teams. Dependency injection is one of them. It is not anything new and has been around for a while. Frameworks like ASP.NET and Java Springboot have been using DI for many years now and NestJs is just an addition to the list.