Dependency Injection
GraphQL Modules lets you use dependency injection among your modules, and lets you inject configs, functions, classes and instances into your modules.
We expose a simple API that covers most use cases concerning backend modules.
We learned not to force using dependency injection too early in the process, because dependency injection makes sense only in some specific use cases, and using it can be recommended only when your codebase is quite large and you need to move fast.
GraphQL Modules lets you choose whether to use dependency injection or not.
#
ProvidersLet's start by creating a simple class UserProvider
with the @Injectable()
decorator.
modules/my-module/user.provider.ts
Now, let's add this Provider
to our GraphQLModule
:
Now, let's implement the Query.user
resolver as a simple function inside UserProvider
:
The lifecycle of a
Provider
is by default a singleton. So you can use the implemented functions as utility and still usethis
to save global variables.
To use this function from our Provider in the actual resolver implementation, get injector
from context
:
Now our resolver is just a proxy to our implementation: we can easily replace UserProvider
with mocks during tests.
We can use UserProvider
and getUserById
from other modules.
#
Import providers from other modulesTo use Provider
from other modules, you can just inject it using @Inject(INJECTION_TOKEN)
.
Injection tokens are used just for identifying your value and fetching it from the available injectables.
It could be either class
, string
or Symbol
.
To get OtherProvider
from MyProvider
, do the following:
We added
private
on the argument declaration; it's just a TypeScript trick to declare a class member and set it at the same time, and so nowthis.otherProvider
is available to use.
#
Injection TokensIf you wish to decouple the actual class implementation and the injection token, you can create your own injection token and declare it this way:
This way, you can ask for the actual value of MY_CLASS_TOKEN
from other providers, without knowing the specific implementation:
This is a very common and useful design pattern related to dependency injection. The power of TypeScript interfaces enables you to use it easily.
#
Custom InjectablesYou can also create custom Provider
s (which are non-classes) with injection tokens.
It's useful when you want your module to get something from outside with a specific signature.
You can use custom injection tokens to identify your injectables.
#
ClassWe have already learned how to provide classes:
just specify the class in the list of providers
in your module declaration.
Or use another class which has same interface.
The lifecycle of a class provider is a singleton by default, i.e. they are created only once and you are using the same instance for all GraphQL executions. But you can change the lifecycle using Provider Scopes.
#
ValueValue providers are an easy way to pass an existing instance of class
or any other value that you wish to make available to @Inject
.
You can use any value, and attach it to an injection token.
You can use value injectables to inject instances and global injectables, such as
Logger
instance, database connection/collections, secret tokens etc.
#
FactoryFactories provide another way to pass an instance of provider
. The return value of the factory function is used as the value of the provider.
#
HooksOnInit
hook#
This hook is called once when your application is started.
Example:
OnRequest
hook#
By defining this hook as a method in your class provider, you can get access to useful information: the top GraphQLModule
instance, GraphQL Context, and the network session.
The
OnRequest
hook is called on each HTTP GraphQL request with a singleModuleSessionInfo
parameter.
OnResponse
hook (experimental)#
It takes the same parameter as the OnRequest
hook but gets called right before the server sends the HTTP response to the client.
Example:
The
OnResponse
hook is called on each HTTP GraphQL request with a singleModuleSessionInfo
parameter.
OnError
hook (experimental)#
It takes one parameter of the type Error
; it gets called when any error is thrown during execution of resolvers.
This hook is not called if there is an error on dependency injection or context building because then dependency injection completely stops.
OnConnect
hook#
This hook is similar to OnRequest
but it is called on the initialization of the WebSocket connection.
It is exactly the same with the OnConnect
hook passed to subscriptions
in SubscriptionServer.
See also Apollo document on authentication over WebSocket.
Example:
The
OnConnect
hook is called once for each WebSocket GraphQL connection.
OnDisconnect
hook#
This hook is similar to OnResponse
but it is called on the termination of the WebSocket connection.
It is exactly the same as the OnDisconnect
hook passed to subscriptions
in SubscriptionServer.
See also Apollo document on authentication over WebSocket.
The
OnDisconnect
hook is called once for each WebSocket GraphQL connection.
The other
OnOperation
andOnOperationComplete
hooks work similarly to the GraphQL Subscription Server implementation. See also the document of subscriptions-transport-ws.
#
Provider ScopesYou can define different lifecycles for your provider. You have three options.
See also Scoped Providers in GraphQL Modules Dependency Injection.
#
Application Scope (by default)If you define a provider in this scope (which is default), the provider will be instantiated on the application start and remain the same in the entire application and all the following requests. The providers in this scope can be considered as a shared state across all users’ interactions with our application. It basically means that the instance will be treated as a singleton.
#
Session ScopeWhen a network request arrives at your GraphQL server, the GraphQL server calls the context factory of the parent module. The parent module creates a session injector and instantiates session-scoped providers with the session object which contains the current context, session injector and network request. This session object is passed through the module's resolvers using the module's context.
In other words, providers defined in the session scope are constructed at the start of the network request and then kept until the network request is closed. Whereas application-scoped providers are kept during the application runtime and shared between all the following network requests and resolvers in the requests, this type of providers would not be shared between different requests; in resolver calls those belong to the same network request.
This session scope is kept on memory for all the following network requests of the same connection if the connection uses WebSocket. For regular HTTP, it is terminated immediately.
#
Request ScopeIf you have request-scope'd providers in your GraphQL Module, these providers are generated in every injection. This means a request-scoped provider is never kept by neither the application state, nor the session state. So this type of providers works just like a factory. It creates an instance each time you request from the injector.
ModuleSessionInfo
Provider#
Built-in Every GraphQL-Module creates on each network request a ModuleSessionInfo
instance that contains the raw request from the GraphQL Server, the SessionInjector
with session-scoped instances and application-scoped instances, and the Context
object constructed by contextBuilder
of the module.
Note that you cannot use this built-in provider in the application scope.