Dependency Injection in GraphQL-Modules
Tags: di, graphql, graphqlmodules
Published: 2021-07-29 08:54:09

> This article was published on Friday, January 11, 2019 by [Arda Tanrikulu](https://twitter.com/ardatanrikulu) @ [The Guild Blog](https://the-guild.dev/)

## Why Not Inversify / Angular / NestJS?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](https://github.com/inversify/InversifyJS) 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](https://angular.io/guide/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.**## EncapsulationWhile we were still using ***Inversify***, we used to have a single `GraphQLApp` top module. Regular
GraphQL Modules cannot import each other, but can be imported by that `GraphQLApp` object. Also
these modules didn't have an ***encapsulation***, because all the ***providers***, ***schemas*** and
***resolvers*** were getting concatenated without any ***encapsulation***. After that, we decided to
provide complete Modular approach, and make everything module. Now Each module has its own injector,
valid schema and context which is independent from its parent modules. Also, each module is
constructed by using the imported modules' DI containers, schema contents and context builders.If you want to know more about ***encapsulation*** see the blog post about it;/blog/modular-encapsulation-graphql-modulesFor 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;```typescript
const AppModule = new GraphQLModule({
    imports: ({ config: { connectionStuff }}) => { // getting the configuration in schema and DI container generation phase
        DatabaseModule.forRoot({ // Define this configured module as the default instance inside AppModule
            connectionStuff
        }),
        OtherModuleThatUsesDB,
    },
    configRequired: true, // makes schema and DI container prevent to be requested without a valid configuration
})

const DatabaseModule = new GraphQLModule({
    providers: [SomeDbProvider],
    configRequired: true, // makes this module prevent to be used without a valid configuration in any part of the application
});

@Injectable()
export class SomeDbProvider {
    constructor(@ModuleConfig() config) { // get configuration into the provider
        // some logic with the configuration
    }
}

const OtherModuleThatUsesDB = new GraphQLModule({
    imports: [
        DatabaseModule.forChild() // Use the configured DatabaseModule in the higher level, prevent this module to be imported without unconfigured DatabaseModule
    ]
})
```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.*## HierarchyWe also have another problem about the hierarchical approach of existing DI implementations. Let me
give an example;*   ***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 ProvidersIn contrast with client side application, your DI system wouldn't need to have different scopes like
Application Scope and Session Scope. In our implementation, we can define providers that have a
lifetime during a single GraphQL Network Request and not the whole the application runtime; because
some providers needs to belong in a single client. Unfortunately, you need to find tricky ways to
manage scoped providers if you use existing DI libraries like ***Inversify***.> 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 the application scope.You can read more about the different scopes that we have in GraphQL-Modules here:/blog/graphql-modules-scoped-providers## Comparisons with Other DI Implementations of Frameworks### Comparison with InversifyTo provide *true encapsulation*; we should have an ***injector/container*** with multiple children,
and these children must not know each other and even its parent. In ***Inversify***, they have
something like that which is called ***Hierarchical*** ***DI***. In this feature of ***Inversify***,
the`parent` term can be misunderstood. because an injector seeks for its own providers then looks
for the other injector that is being defined as `parent` in that feature. But, we need is more than
that. We want to have multiple `parent` according to their logic.> 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 DIIn Angular, there is one injector that is shared across all the application while there are
different encapsulated injectors belonging to each module in different scope.> Our Dependency Injection implementation is more strict in terms of encapsulation than Angular's.```typescript
import { NgModule } from '@angular/core'
import { BarModule } from '@bar'
import { FooModule, FooProvider } from '@foo'

@NgModule({
  imports: [FooModule, BarModule],
  providers: [
    {
      provide: FooProvider,
      useClass: MyFooProvider
    }
  ]
})
export class AppModule {}
```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.```typescript
import { BarModule } from '@bar'
import { FooModule, FooProvider } from '@foo'
import { GraphQLModule } from '@graphql-modules/core'

export const AppModule = new GraphQLModule({
  imports: [FooModule, BarModule],
  providers: [
    {
      provide: FooProvider,
      useClass: MyFooProvider
    }
  ]
})
```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 lifetime 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*](/blog/graphql-modules-scoped-providers) in the beginning of this article### Comparison with NestJS' DI ImplementationNestJS is a server-side model-view-controller full-fledged framework that has all the principles of
MVC Backend framework. exposing a GraphQL API is only one of its various features. Our goal with
GraphQL Modules was to create a set of tools and libraries that answer needs for the GraphQL
ecosystem, and can be (and should be) used by NestJS users as well.> 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' 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*](/blog/modular-encapsulation-graphql-modules) article.Also, NestJS DI system doesn't share the important feature of defining providers that have 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*](/blog/graphql-modules-scoped-providers) part of this article above.## Working TogetherWe wanted to share the principles behind our implementation in order to get more background to our
community and get feedback from other library authors.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*   [GraphQL Modules — Feature based GraphQL Modules at scale](/blog/graphql-modules)
*   [Why is True Modular Encapsulation So Important in Large-Scale GraphQL Projects?](/blog/modular-encapsulation-graphql-modules)
*   [Why did we implement our own Dependency Injection library for GraphQL-Modules?](/blog/dependency-injection-library-in-graphql-modules)
*   [Scoped Providers in GraphQL-Modules Dependency Injection](/blog/graphql-modules-scoped-providers)
*   [Writing a GraphQL TypeScript project w/ GraphQL-Modules and GraphQL-Code-Generator](/blog/graphql-typescript-modules-codegen)
*   [Authentication and Authorization in GraphQL (and how GraphQL-Modules can help)](/blog/graphql-modules-auth)
*   [Authentication with AccountsJS & GraphQL Modules](/blog/accountsjs-graphql-modules)
*   [Manage Circular Imports Hell with GraphQL-Modules](/blog/manage-circular-imports-hell-in-graphql-modules)