Dependency Injection in Javascript
This article explores dependency injection in Javascript using higher-order functions and compares it against dependency injection in classes.
By Subash Adhikari · · 4 min read

Dependency injection (DI) is a pattern where you pass a dependency in through parameters instead of creating it inside the function or class. It lets you build isolated components and makes it easy to swap those dependencies later when requirements change. Because dependencies come in as parameters, you can also unit test each component in isolation by injecting a mocked version.
In this article I look at dependency injection in Javascript using higher-order functions and compare it with classes. The pattern works in any language that supports standalone functions.
For the rest of the article, let's say we're building a simple app with a Course entity, and each Course has a Lesson. Courses are stored in AWS DynamoDB, and lessons are stored as JSON objects in an S3 bucket.
To understand the pattern, we'll first look at how dependency injection works with classes, then go through some examples with Javascript functions.
Dependency Injection using classes#
Let's start with classes so we have something to compare the higher-order function approach against.

Here we've defined a Course class with two methods. courseById gets the course from DynamoDB, and addLesson adds a lesson to S3. Both documentClient and S3 from aws-sdk are injected.
To use this class in our application, we first need to create an instance of Course. Unless we're using a dependency injection framework, we'd usually do that with a Factory class.

We can then call Factory.createCourse in our application to create a course instance and invoke the method we need. Factory could also grow other static methods to create instances of other entities, like Lesson.

If the requirements change later and we need to store Course somewhere else, we only update Factory or Course. Our business logic stays untouched.
Dependency Injection using the higher-order function#
Notice that with classes, we always need an instance of both S3 and DocumentClient before we can get an instance of Course.
Imagine a webhook or AWS Lambda Function whose only job is to getCourseById. We'd still end up creating an S3 instance, even though that request never uses it.
In a real application, setting up and tearing down each dependency can involve much more complex logic, and that can hurt performance.
So let's look at dependency injection at the function level.
The pattern is built on a function that returns another function (a higher-order function). The higher-order function accepts every dependency the inner function needs to do its job.

Here we create a function called makeGetCourseById. Its job is to create a function we can use to get a course by id.

We use it in our application code like this. The same pattern works for addLesson.

The idea is that we create individual functions like getCourseById or addLesson once and pass them around the application. Without the higher-order function, we'd have to pass an instance of documentClient or s3 every time we wanted to get a course by id.
This is how the higher-order function looks inside an AWS Lambda Handler.

Creating services with higher-order functions#
Sometimes we want a collection of methods we can use without initializing each one separately. For example, we might want to attach a service to the GraphQL context so resolvers can use its methods.
To do that, we can write a function that returns an object containing a collection of functions.

Then we use it in our application code like this. Keep in mind that, where possible, we create the courseService instance once per application execution, and we can even cache it if needed.

Passing methods around#
We'll often want to use one higher-order function inside another. For example, we might want to check that the Course exists before adding a lesson. We can do that by accepting getCourseById and S3 as parameters.

The application code looks like this:

Summary#
We looked at two ways to do dependency injection in Javascript. One uses a Class and a Factory. The other uses higher-order functions to inject dependencies at the function level. Depending on your use case, pick one approach and stick with it. I find higher-order functions simpler to work with and more flexible than Javascript classes.
Related posts
Thanks for reading. You can also follow along on X or with RSS.