Back to all blogposts

JavaScript dependency injection in Node – friend or foe?

Adam Polak

Adam Polak

VP of Technology

Dependency injection in JavaScript 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 JavaScript dependency injection in Node.js actually is.

What is a 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 the 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.

Need top-class Node.js development?

🛠 Rely on Poland’s biggest Node team to build or expand your app with success. Technology professionals rate our software development delivery 4.9 on Clutch.

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 the users repository. This approach allows us to unit-test much more complex cases, without the need for 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 the 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

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 is 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 solution 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).

Dependency injection solutions – friends or foes?

Node dependency injection provides a lot of flexibility not only when it comes to easier unit testing and testing in general, but also when working with an app. It allows us to create and implement modules that are fully independent of each other and it is always a welcome thing in modern object oriented programming and software development. Most developers can appreciate the benefits dependency injection pattern provides.

By using some additional tools, for example a dependency container, you can make the task of modules building ever easier, avoiding quite a few issues brought up in this article. At TSH we use DI in every Node.js project, in both small and large projects, and we can’t imagine working with our code without it.

I’m going to return to the topic of dependency injection in Node as well as well as other design patterns such as dependency inversion and dependency inversion principle in the coming articles so stay tuned!

💡 Read more

You may also like

What would you like to do?

    Your personal data will be processed in order to handle your question, and their administrator will be The Software House sp. z o.o. with its registered office in Gliwice. Other information regarding the processing of personal data, including information on your rights, can be found in our Privacy Policy.

    This site is protected by reCAPTCHA and the Google
    Privacy Policy and Terms of Service apply.

    We regard the TSH team as co-founders in our business. The entire team from The Software House has invested an incredible amount of time to truly understand our business, our users and their needs.

    Eyass Shakrah

    Co-Founder of Pet Media Group


    Thank you for your inquiry!

    We'll be back to you shortly to discuss your needs in more detail.