How to overcome dependency injection on node.js?

Hüsnü Tapan
Trendyol Tech
Published in
4 min readDec 22, 2021

Sometimes we need to decrease coupling between classes and their dependencies. Also flexible and spread easily different layer.

Dependency injection comes to mind at that moment. What is the purpose of Dependency injection? Why do we need it?

It has incredibly nice features; these make our code cleaner and more readable.

So at first glance, there are some benefits everyone can have an idea of even if they don’t involve code; the other is also most important one is our code become loosely coupled. So If we declare specifications accurately, We don’t get involved in managing application instances between each other. After that, we can focus our application development without trouble.

Today, I will demonstrate how to implement IoC container on the node.js project. IoC manages dependencies for us and provides decoupling and maintainable architecture

Why did we choose to use it?

As I mentioned before, there are many reasons for using this structure. Primarily node.js is a lightweight framework and works as a non-blocking system. Suppose we built inversion of control over there, which makes it so powerful. It will eliminate fragile things. We chose this structure because several teams are touching the same code point. That library provides a
maintainable code base and gets rid of a domino effect.

It facilitates the maintenance of the application. This library is used for dependency injection for this reason. Today, we will dig into how to use and implement IoC on node.js?

First of all, we need to prepare the infrastructure of our project

We should configure tsconfig.json file as below

{
"compilerOptions": {
"outDir":"./build",
"target": "es5",
"lib": ["es6","DOM"],
"types": ["reflect-metadata"],
"module": "commonjs",
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

“reflect-metadata” is a significant point because it allows us to do runtime reflection. If you are developing other programming languages, refer to get instance on the runtime by reflection. Therefore we should declare it here.

After that, we need to create a ioc.ts file to register whole components there. We no longer care about object lifecycle inversify library is going to manage object runtime instead of us.

export const container = new Container();container.bind<EmployeeRepository>(EmployeeRepository).toSelf().inSingletonScope();container.bind<EmployeeService>(EmployeeService).toSelf().inSingletonScope();

All inversify instances are involved here. If we desire any runtime IoC objects, we should define them here. Also, we can use different application scopes we can define according to our requirements. I am going to use all instances as a singleton.

Now it’s time to build for the registration of application instances. I denote the service and repository layer to fetch dummy data from the repository through the entry point side.

@injectable()
export class EmployeeService {
public employeeRepository: EmployeeRepository;

constructor(@inject(EmployeeRepository) employeeRepository: EmployeeRepository) {
this.employeeRepository = employeeRepository;
}

public getEmployees(): Employee[] {
return this.employeeRepository.getEmployees();
}
}

We should sign with an injectable annotation which registered component and communicate with other application components.

@injectable()
export class EmployeeRepository {

public employee: Employee[] = [
{id: 1, name: "employee1", age: 30},
{id: 2, name: "employee2", age: 23},
{id: 3, name: "employee3", age: 25},
{id: 4, name: "employee4", age: 24}
];

constructor() {
}

public getEmployees(): Employee[] {
return this.employee;
}
}

Later on, we prepared an employee list, and when triggered “getEmployees” method, it will deliver to employees data.
Now we are ready to nudge.

const employeeService = container.get<EmployeeService>(EmployeeService)

console.log(employeeService.getEmployees())

Multiple Injection Support

Inversify supports the multi-inject feature, enabling us to use it on interfaces and abstract classes.We can apply the polymorphism strategy if we wish to inject the whole inheritor class. It solves our business with a single injection. Let’s examine it.

First, we have an interface or abstract class for concrete classes,

interface DepartmentRepository {
getDepartmentName(): string;
}

Now we can implement DepartmentRepository in the other classes

@injectable()
export class FinanceRepository implements DepartmentRepository {

getDepartmentName(): string {
return "Finance";
}
}

and

@injectable()
export class HrRepository implements DepartmentRepository {

getDepartmentName(): string {
return "Human Resources";
}
}

We completed our implementation, but we forgot to register those components on the inversify container.

container.bind<DepartmentRepository>("DepartmentRepository").to(FinanceRepository).inSingletonScope();container.bind<DepartmentRepository>("DepartmentRepository").to(HrRepository).inSingletonScope();container.bind<DepartmentRepository>("DepartmentRepository").to(SalesRepository).inSingletonScope();

Above adding the same type as same alias repository class and if we could get DepartmentRepository instance from the application context it will return regarding all of the subclasses. Let’s try to figure out how we can inject.

public departmentRepository: DepartmentRepository[];

constructor(@multiInject("DepartmentRepository") departmentRepository: DepartmentRepository[]) {
this.departmentRepository = departmentRepository;
}

We got rid of all sub-class inject through the constructor. It solved our complicated issue.

Once completed, we can build our code for typescript convert to pure javascript. Therefore, we should install tsc(typescript compiler) globally to our machine. Let’s check out my package.json script side.

"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc",
"start": "node build/index.js"
}

As I mentioned before, we build our project later tsc compile the whole ts file under the build package. Sequentially, we can run to start command to search to the project’s entry point. I declared index.js as an entry point.

Finally, let’s move on to the index.js; we invoke to registered component and call our method on the following line.

const employeeService = container.get<EmployeeService>(EmployeeService);

const departmentService = container.get<DepartmentService>(DepartmentService);

console.log(employeeService.getEmployees());

console.log(departmentService.getDepartmentNames());

To sum up, we can fetch the list of employee list from the entry point of code. First, we reveal to which application instance will do the job we got from the IoC container,

As we know, all instances are registered as singleton; therefore, whenever we call any instance from the application context, it will return to a single instance.

Finally, we invoked the employee method and returned to added dummy employee lists and department names.

You can reach out to the official inversify Github page.

Find more detail of this article, visit my Github repository

Feel free to comment and clap if you enjoyed :)

--

--