Back to all blogposts

JavaScript dependency injection in Node – friend or foe?

Adam Polak

Adam Polak

VP of Technology

Dependency injections, when implemented properly, offer a lot of benefits. They can make your system more flexible and modular, and even help you write testable code. Unfortunately, the implementation is not straightforward and it is especially rarely used when it comes to Node.js. But today I’m going to show you that not only is dependency injection in Node.js a thing, but also that it can be done with functions too! Let’s get to coding!

When you start working with Node.js, one of the first concepts you learn is the module pattern. There is nothing better than reusability and modularity, isn’t it?

But 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 you will learn – the dependency injection in Node.js overview

In today’s article, I’m going to show you the potential of dependency injections in Node.js to make your life easier. Specifically, you’re going to get:

  • practical explanation of the dependency injection concept (We’re going to write code examples),
  • an overview of dependency injection solutions in Node.js for both object oriented programming and functions (yes!),
  • a quick introduction into setting up a DI-based project, in particular the orchestration and tooling.

Let’s go for it!

A big part of our Node.js development work is done on AWS infrastructure. As AWS Advanced Partner, we also conduct AWS consultancy according to well-established best practices. Learn more about our AWS expertise.

What is a dependency injection in Node.js?

Dependency injection in Node.js or JavaScript in general 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.

To put it in other words, dependency injection in Node.js 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.

Dependency injection is used in all kinds of languages and environments. Here’s a list of the most popular ones in terms of microservices usage as per the State of Microservices report. DI in .NET shows that at the end of the day usage across different languages is pretty similar.

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. Let’s make a dependency injection container. We’re also going to create some configuration files.

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

Solutions for dependency injection in Node.js – friends or foes?

That’s about it! Let’s sum things up:

  1. 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.
  2. 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.
  3. 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.js as well as well as other design patterns such as dependency inversion and dependency inversion principle in the coming articles so stay tuned!

💡 Do you want more expert Node.js content? Check out these pieces

Are you looking for Node.js developers who can use dependency injection in commercial projects?

Our portfolio is full of practical implementations of similar techniques and concepts. Check it out!

Just released!
The State of Frontend 2024

Performance is the #1 challenge in 2024. 6028+ answers analyzed.

Read now

The Software House is promoting EU projects and driving innovation with the support of EU funds

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

    Thanks

    Thank you for your inquiry!

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