Why did we implement our own Dependency Injection library for GraphQL-Modules?

Why not Inversify/Angular/NestJS?

Jan 11, 2019 · 8 min read

When designing GraphQL Modules, we tried different approaches to encapsulate modules safely. We created a solution where you won’t need to use Dependency Injection when you start, but after you are getting to a certain scale, you can make use of our separate DI library to ease the separation and make the boundaries stricter, only at the point when it really makes sense and helps you. When you get to that point, DI helps you mostly for ease of mocking providers for testing and encapsulation for less error prone implementation.

In the early stages of GraphQL-Modules, it used to have Inversify internally for Dependency Injection in GraphQL-Modules. Inversify is a platform-agnostic Dependency Injection library written in JavaScript. Unfortunately, Inversify didn’t fit our needs for modular DI (Dependency Injection) due to some critical reasons that we’ll expand on this article.

That’s why, we implemented our own platform-agnostic Dependency Injection library called @graphql-modules/di which is independent from @graphql-modules/core, and it can be used by itself.

It supports factory, class and value providers that can have Symbol, string, number, function and object tokens in a provide definition, and it can address constructable classes, factory functions and constant values as injected values. It has some features that are similar to Angular’s Dependency Injection that is mentioned in their documentation. In this article we’ll explain the similarities and differences between those solutions.

Let’s start with the principles we wanted to have in our DI logic, and we will finish with the comparisons with Angular and NestJS.


If you want to know more about encapsulation see the blog post about it;

For example, if you have a DatabaseModule in your application that has to be shared across the whole application, and it needs to take a custom provider that will be defined by the user. What you will do in those DI implementations is to create a provider by decorating it with a global scoped provider; then put it in ApplicationModule. However, this would violate the encapsulation principle of the modular approach.

To summarize; while your DatabaseModule is imported by other modules in the lower level and that module will use a provider in its parent. It mustn’t know what imports it.

We handle this in a different way in GraphQL Modules by passing configuration by obeying the encapsulation rule;

The three benefits we have with this type of DI and modular system;

  • AppModule is protected from an unsafe call without a valid configuration that is needed in the internal process (DatabaseModule); thanks to configRequired .
  • DatabaseModule is protected from an unsafe import without a valid configuration that is needed in the internal process (SomeDbProvider); thanks to configRequired again!
  • OtherModuleThatUsesDb is protected from an unsafe call or import without a definition of a well-configured DatabaseModule.


  • A DI Container has FooProvider
  • B DI Container has BarProvider
  • C DI Container has BazProvider
  • D DI Container has QuxProvider
  • A imports B
  • B imports D
  • B imports C

For example the following case will happen in our DI;

  • FooProvider cannot be injected inside B while BarProvider can be injected by A; because the injector of B only have its providers and D’s and C’s (BazProvider and QuxProvider).

As you can see in the comparison with Inversify, Inversify can attach only one child DI container in a parent DI container; and this wouldn’t fit our hierarchy-based modular approach. For example, you cannot make B extend both D and C by keeping D and C encapsulated. If you merge all of them into the one injector like Angular does, D can access C’s providers without importing C.

Scoped Providers

In server-side applications, every client request has its own scope that we call `session scope`. We think this session scope should be able to have its own providers and DI logic outside of the application scope.

You can read more about the different scopes that we have in GraphQL-Modules here:

Comparisons with Other DI Implementations of frameworks

Comparison with Inversify

Every module has its own injector together with its children’s injectors. So, every module can interact with its children’s providers with its own injector.

You can read more about Hierarchy and Encapsulation in the beginning of this article.

Comparison with Angular’s DI

Our Dependency Injection implementation is more strict in terms of encapsulation than Angular’s.

This is an example top module written in Angular. Let’s assume FooProvider originally implemented and defined inside FooModule and used by BarModule . When FooProvider token is replaced by MyFooProvider in AppModule scope, BarModule also uses our MyFooProvider inside its logic. This explicitly violates encapsulation in the modular approach, because Angular is supposed to send to BarModule FooModule ‘s FooProvider implementation instead of the one defined outside of BarModule ; because it shouldn’t know what is defined in the higher level.

However, GraphQL-Modules will send to BarModule the correct FooProvider , because BarModule imports FooModule not AppModule . So, MyFooProvider will be sent if the current injector is inside AppModule scope.

In GraphQL-Modules, if A imports B and B imports C, A can access C’s scope. But, C cannot access the scopes of B and A. This allows us to create a safely built GraphQL Application and reduces the error prone. You can read more about Hierarchy and Encapsulation in the beginning of this article.

Angular doesn’t care about that. it has a single injector for the all application. This may cause some problems for debugging on a large scale backend application.

That ability is not so important for Angular applications; because they are running on the browser, not on the server. But still you can notice the similar behavior to GraphQL-Modules DI’s if you load an Angular module lazily using router’s loadChildren property.

And Angular’s DI library doesn’t differentiate Application and Session scopes because it doesn’t need that. An Angular application has a life time until window is terminated by closing it or refreshing page; the session and application runtime can be considered same in the client application. That’s why, our DI implementation is more complex than Angular’s as it need to handle multiple clients by running a single application. You can read more about Scoped Providers in the beginning of this article

Comparison with NestJS’s DI implementation

The principles and approaches we’re trying to apply in GraphQL Modules is not only for Dependency Injection but also for GraphQL Schemas and Context.

In comparison to Nest’s goals, GraphQL-Modules is a platform agnostic library that can be used even with the plain graphqljs package without a server; because it was designed to work exactly same in all GraphQL Server platforms such as Apollo, Yoga, graphql-express etc. For example, you can use Apollo’s data sources with graphql-express; because GraphQL Modules passes its own cache mechanism into the dataloaders thanks to our independent DI logic.

The result of a GraphQLModule is a context builder and a schema while NestJS’s GraphQLModule is a NestJS module that exposes an API using Apollo.

NestJS has its own dependency injection system which is similar to Angular’s but more strict; you can define both global (not by default like Angular) and encapsulated providers at the same time. We preferred not to have global providers like Angular (you can see how it works above in the Angular comparison); because we consider it can be more error prone and it has to be done by passing configuration. You can read more with an example about passing configuration and keeping safe boundaries between your modules in the Encapsulation article.

Also, NestJS DI system doesn’t share the important feature of defining providers that has the life-time of the client request (we call it session scope). We think a backend system would need to differentiate DI containers according to these different scopes. You can read more about it in Scoped Providers part of this article above.

Working together

That is also one of the reasons we created our DI library as a separate library, not only so it would be optional to our users, but also to potentially share that implementation with other libraries like NestJS and Inversify for example.

All posts about GraphQL Modules

  1. Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?
  2. Why did we implement our own Dependency Injection library for GraphQL-Modules?
  3. Scoped Providers in GraphQL-Modules Dependency Injection
  4. Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator
  5. Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)
  6. Authentication with AccountsJS & GraphQL Modules
  7. Manage Circular Imports Hell with GraphQL-Modules

Follow us on GitHub and Medium, we are planning to release many more posts in the next couple of weeks about what we’ve learned using GraphQL in recent years.

The Guild

The Guild

Thanks to Dotan Simha and Urigo


Written by

JavaScript Enthusiast, member of The Guild https://github.com/ardatan

The Guild

The Guild

The Guild

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade