Understanding Dependency Injection in NestJS

Bharat Hegde
5 min readSep 18, 2023

--

This blog is not an introduction to NestJS, but its more of how NestJS uses dependency injection.

To understand dependency injection, we first need to understand things around Inversion of Control Principle.

It states that Classes should not create instances of its dependencies on its own.

Let’s dive this with 3 different examples / pattern

The Bad example

export class MessageService {
messagesRepo: MessagesRepository

constructor() {
this.messagesRepo = new MessagesRepository()
}
}

In this example here, the MessageService class creates its own instance of MessagesRepository . It is not following the Inversion of Control principle. So this is a bad example.

The Better example

export class MessageService {
messagesRepo: MessagesRepository

constructor(repo: MessagesRepository) {
this.messagesRepo = repo
}
}

In this example, the constructor receives the messagesRepo instance and it does not need to worry about the instantiation of MessagesRepository object. But the slight downside is that the constructor always specifically needs MessagesRepository object always to be passed in.

The Best example

interface Repository {
findOne(id: String);
findAll();
create();
}

export class MessageService {
messagesRepo: Repository

constructor(repo: Repository) {
this.messagesRepo = repo
}
}

In this example also, the constructor takes in an argument. But this time it can be any object that implements the Repository interface. It can either be MessagesRepository or it can be any other, but it should satisfy the interface.

How would that be helpful?

Consider this scenario. In the development / testing environment, when MessageService instance is being instantiated, it would have a MessageRepository object as part of the constructor. If you are aware of NestJS concepts then you very well know that repositories usually interact with databases. And invoking the databases usually means there is some IO and takes some time to process it.

In testing environment (may be automated tests), we want to run test scripts which needs to run fast. We might not want to do actual database calls. So the MessageService in test environment will be instantiated with a FakeMessageRepository object which has implementation of the interface Repository without the database calls. This would make the tests extremely fast.

In short, the MessageService can be instantiated with any object that satisfies the interface.

Some other benefits of Inversion of Control principle:

  1. Decoupling and Modularity: This promotes modularity and improves the system’s maintainability, scalability, and reusability.
  2. Testability: By allowing dependencies to be injected or provided externally, it becomes easier to substitute real implementations with mock objects for testing purposes.
  3. Flexibility and Extensibility: New components can be added or existing components can be replaced without modifying the existing code, simply by configuring the IoC container appropriately.
  4. Configuration Management: IoC containers often manage configuration details, making it easier to change settings without modifying the application code. This supports the principle of “separation of concerns” by keeping configuration separate from the application logic.
  5. Centralized Control: IoC containers provide a centralised mechanism for managing object creation and lifecycle. This centralisation can simplify the overall design and promote consistent behaviour across the application.

But…..

Its not so beautiful always. Again, if you are familiar with NestJS you would know that in order to create a controller instance, you would need to create a service instance and a repository instance

const messageRepo = new MessagesRepository()
const messageService = new MessageService(messageRepo)
const messagesController = new MessagesController(messageService)

As an application grows in the size, a controller might take in multiple services. Each service might take in different instances of repositories.

const messageRepo = new MessagesRepository()
const userRepo = new UserRepo()
const messageService = new MessageService(messageRepo)
const userService = new UserService(userRepo, messageRepo)
const messsagesController = new MessagesController(userService, messagesService)

This complexity would increase if multiple service would need multiple repositories or a controller would need multiple services. The amount of code to instantiate a single controller would be too much.

Seems like the Best is not the best. If somehow we could make use of the Better method (i.e. the second approach) to do things in IoC way, it would be great.

Dependency Injection to the rescue

Dependency Injection is all about making use of IoC and not having to create a ton of different instances every time you need to create a controller.

NestJS maintains a Dependency Injection Container (also called Injector). This an object with couple of different properties in it. For simplicity, it maintains 2 different sets of information

i. List of classes and their dependencies

ii. List of instances that have been created

This is how it works:

  1. The NestJS app starts up
  2. It looks up for all the classes other than the controller. It checks the MessageService , it looks at its constructor and understands that it has a dependency to MessagesRepository.
  3. It adds MessageService to the list of classes and dependency section
  4. It looks at the constructor of MessagesRepository, and finds that it has no dependency. It adds MessagesRepository to the list of classes and dependency section
  5. It creates an instance of MessagesRepository and adds it to the list of instances section
  6. Next it creates the instance of MessagesService since it now has the instance of MessagesRepository and adds it to the list of instances section
  7. Next it instantiates the controller. The controller can now be instantiated since the dependency of MessageService is already available.
Nest DI Container

DI Container Flow

  1. At start-up, register all classes with the container
  2. Container will figure out what each dependency each class has
  3. Whenever its needed, the container is requested to create an instance of a class for us
  4. Container creates all required dependencies and gives us the instance
  5. Container will hold on to the created dependency instances and re-use them if needed

The @Injectable marks the registration of the class into the DI container. We do not register controller as part of DI container, because controller is a consumer. The instantiation of controller is taken care by Nest.

In the module file, when you define a providers array, you are actually defining the things that can be used as dependencies for other classes.

// message.service.ts
import { Repository } from 'typeorm';

@Injectable()
export class MessageService {
messagesRepo: MessagesRepository

constructor(@InjectRepository(Message) private repo: Repository<Message>) {}
}

// messages.entity.ts
@Entity()
export class Message {

}

// messages.module.ts
@Module({
controllers: [MessagesController],
// things that can be used as dependencies for other classes
providers: [MessageService],
imports: [TypeOrmModule.forFeature([Message])],
})
export class MessagesModule {}

The @InjectRepository decorator may be a little bit weird and out of sync with the @Injectable way of things. But what its saying is that instance repo is of type TypeORM Repository that deals with instances of Message, hence the generics is used Repository<Message> to denote the same. The @InjectRepository decorator is an aid to dependency injection which tells the DI container that it needs the Message repository.

The DI figures out the dependencies and gives the instances during run time. But unfortunately, the DI is not good with generics and we use the @InjectRepository because we are using the generic type.

Hope this gave an idea of how DI works.

Thanks for reading. Any comments feedback are always welcome ❤️

--

--

Bharat Hegde

Full Stack Developer | Javscript | ReactJS | Ruby On Rails | Gaming | Reading