Dependency injection for server-side applications

Every Angular developer loves the dependency injection system the ng core provides. DI is a intelligent way to manage all the applications classes and therefore it is definitly worth learning.
In this article I want to demonstrate a way to implement easy-to-understand and high usability (developers point of view) object-oriented design with TypeScript in NodeJS. This is not a TypeScript basics article and expects at least a little TS programming experience.

What is dependency injection?

Depenency injection is a principle that intelligently manages your applications classes and prevents redundant instatiations. A DI system usually implements containers which store class definitions and instances. Requesting a class instance results in a lookup of these containers and, if not exist, it creates and returns the instance or otherwise returns the existing instance.
In a hierarchical dependency injection the child containers are aware of the parents. If a class is requested to a child container, the DI system checks the container, and, if not recognized, it looks in the parents container. This is how you can share data and logic between multiple modules with just a single class instance.

Setting up a new project

Create a new directory, e.g. diproject, which will be the root ./ of the application, and run
cd diproject npm init # answer questions npm i typescript --save-dev npm i fl-node-di
To get started create a new file called ./tsconfig.json, and enable the experimentalDecorators flag. It could look like
{ compilerOptions: { experimentalDecorators: true, outDir: './dist' module: 'commonjs', target: 'es2016' } }
Listing: tsconfig.json

Building the application

At first we create a root ApplicationModule in a file thats located in ./src/application.module.ts. This is the module on top of the dependency injection hierarchy.
import { DatabaseModule } from './modules/database/database.module'; import { Http2Module } from './modules/http2/http2.module'; @FlModule({ imports: [ DatabaseModule, Http2Module ] }) export class ApplicationModule {}
Listing: application.module.ts
Create an ./src/index.ts file with the following content. This will be the applications entrypoint. It simply imports the ApplictionModule and creates an instance.
import { ApplicationModule } from './application.module.ts'; const app = new ApplicationModule();
As you might have seen, we import the DatabaseModule and the Http2Module in our ApplicationModule. These modules are treated as children of the root module. Means that all provided and declared classes in the ApplicationModule are also available in the child modules but not the other way around.
Let's create the children. Starting with the DatabaseModule in ./src/modules/database/database.module.ts. We declare a CreateDatabaseComponent and provide a DatabaseService. The DatabaseService will be available with a single instance in all declared classes of the DatabaseModule.
@FlModule({ declarations: [ CreateDatabaseComponent ] providers: [ DatabaseService ] }) export class DatabaseModule {}
And the Http2Module in ./src/modules/http2/http2.module.ts.
import { PrepareRoutesComponent } from './componenets/prepare-routes'; import { RequestHandlerService } from './services/request-handler'; @FlModule({ declarations: [ PrepareRoutesComponent ], providers: [ RequestHandlerService ] }) export const Http2Module {}
Similar to the database module the PrepareRoutesComponent in ./src/modules/http2/components/prepare-routes.ts is capable of the RequestHandlerService. It can be injected as follows.
import { RequestHandlerService } from '../services/request-handler'; @Component() export class PrepareRoutesComponent { constructor(@Inject(RequestHandlerService) handler: RequestHandlerService) {} }
But what if we need the databaseService when preparing routes? Usually one needs to create a DatabaseService instance in order to get its feature again. But using DI is the more intelligent approach. It is as easy as exporting the service so that it is stored in the ApplicationModule container.
import { DatabaseService } from './services/database'; @FlModule({ export: [ DatabaseService ], declarations: [ ... ] }) export class DatabaseModule {}
Now it is possible to use the instance in the DatabaseModule and in the Http2Module. Finally your projects structure should be similar to

Conclusion

Hopefully you have got an idea of how useful DI really is, even on server-side applications. It massively increases your codes reusability and prevents redundant class instances.

Comments

Any questions? Want to join the discussion?

Sign in or sign up