Node.js and dependency injection – friends or foes?

3 min

read

Dependency injection is a well-known technique, which can make it much easier to produce independent and scalable modules. It can be used in many programming languages and frameworks. However, when it comes to Node.js, dependency injections are not quite as popular as they could be. To some extent, it’s a result of certain misconceptions. In today’s article, I’m going to show you the potential of dependency injections in Node.js to make your life easier.

When you start working with Node.js, one of the first concepts you learn is the module pattern. According to some, you don’t need dependency injection in Node.js because there’s require. Personally, I don’t really agree with that. Why? Let’s start by clearly establishing what dependency injection in Node.js actually is.

What is dependency injection?

DI is a pattern where, instead of creating or requiring dependencies directly inside a module, we pass them as parameters or reference.

At first glance, it might be quite difficult to understand. But it’s easier than you think.

Let’s imagine a simple service module:

It looks fine. The service is responsible for business logic and user repository is responsible for communication with the data source. But there are two problems here.

First of all, the service is coupled with a specific repository. If we wanted to change it to something else, then we would have to modify the entire code. I’m sure you know one of the common programming rules – program to an interface, not an implementation. This is where we violate that rule!

A second problem is testability of this module. If we wanted to find out if getUsers method works, we would need to stub usersRepository using Sinon, Jest.mock or any other stubbing library.

It looks complicated, doesn’t it? Let’s use dependency injection to fix it up!

What we need to do is to pass usersRepository as a parameter instead of requiring it directly.

As you can see, the service is no longer paired with a repository module, but requires usersRepository to be passed to it. It has a major impact on testability:

We could drop sinon from our dependencies and replace it with the injection of users repository. This approach allows us to unit-test much more complex cases, without the need of stubbing.

What’s more, by decoupling service and repository, you gain the freedom to change implementation at any point. Cool!

Dependency injection in Node.js – classes vs functions

Another reason why dependency injections are not popular in the Node.js ecosystem might be a myth that DI is an OOP-only concept. That’s most definitely not true!

Of course, it’s obvious how to inject dependencies in classes. We have a constructor, where we can inject dependencies one by one or by a single object. At TSH, we’re big fans of reducing the number of params being passed to class/function by enclosing them in object.

You can easily use destructuring to access specific dependencies you need. It’s even better in TypeScript where you can specify required dependencies by type.

But JavaScript is not only about classes (we all know that it’s just syntax sugar). So how can we achieve the same with functions only? The answer is simple – parameters.

We create a function that takes dependencies as parameters, and then returns another function/object with a specific implementation. Because of the closure we created, we have access to dependencies from the inner functions.

As you can see, DI is not only about classes!

See also: Swoole

Orchestration and tooling

The visible downside of DI is the requirement to set everything up beforehand. Following the previous example, if I want to create users service, I need to make a repository, mailer and logger. What’s more, both repository and mailer could have their own dependencies, so I need to create the whole structure.

We need to prepare some things before we are able to create the service. This approach is what we call service container – a special module where all of the public services/factories and so on are orchestrated.

Of course, this one is simple and requires a lot of manual work. Still, there are Node.js DI libraries that can do some of it for us.

There are a few options available. The most popular ones are Awilix, Inversify and TypeDI.
TypeDI and Awilix are quite similar and work with both JavaScript and TypeScript. On the other hand, Inversify is TypeScript-only.

At TSH, we’re mostly using Awilix, but consider TypeDI to be very promising.

By using Awilix, we can create a special container that will resolve dependencies on its own. The only thing we need to provide are base building blocks. In our case, it’s about services like DataSource, Logger or parameters like level or templates.

The rest (UsersService, UsersRepository, Mailer) will be resolved automatically by Awilix.  The only thing we need to provide is a type of resolver (for example, we need to tell that UsersService is a class).

When I call the resolve method on an Awilix container, it goes through all of the constructor/function parameters and checks if there is a dependency with the same name in our container (Awilix resolvement is not based on types – even for TS – but on the dependency name) and tries to resolve it.

By doing this, we don’t need to create our dependencies one by one. We only need to provide building blocks and then call the resolve method to get a specific service. I strongly encourage you to check both Awilix and TypeDI (for TypeScript, it allows to get dependencies by types).

See also: Node.js tutorial for beginners

Friends or foes?

Dependency injection provides lot of flexibility not only when it comes to testing, but also when working with an app. It allows us to create modules that are fully independent from each other.

By using some additional tools such as a dependency container, you can make the task of modules building ever easier. At TSH we use DI in every Node.js project and we can’t imagine to work without it.

Estimate your project





Thanks

Thank you!

Your message has been sent. We’ll get back to you in 24 hours.

Back to page
24h

We’ll get back to you in 24 hours

to address your needs as quick as possible.

Estimation

We’ll prepare an estimation of the project

describing the team compostition, timeline and costs.

Code review

We’ll perform a free code review

if you already have an existing system or a part of it.

Our work was featured in:

Tech Crunch
Forbes
Business Insider

Aplikujesz do

The Software House

Aplikuj teraz

wyślij CV na adres: [email protected]

CopiedTekst skopiowany!

Nie zapomnij dodać klauzuli:

Kopiuj do schowka Copy

Jakie będą kolejne kroki?

Phone

Rozmowa telefoniczna

Krótka rozmowa o twoim doświadczeniu,
umiejętnościach i oczekiwaniach.

Test task

Zadanie testowe

Praktyczne zadanie sprawdzające dokładnie
poziom twoich umiejętności.

Meeting

Spotkanie w biurze

Rozmowa w biurze The Software House,
pozwalająca nam się lepiej poznać.

Response 200

Response 200

Ostateczna odpowiedź i propozycja
finansowa (w ciągu kilku dni od spotkania).

spinner