How To Implement Functional Dependency Injection As A Javascript Developer And How To Improve It Using Fastify
Understand Dependency Injection and implement it without using classes.

Dependency Injection is not a feature provided by frameworks.
Many frameworks indeed provide a way to use Dependency Injection. These tools usually implement all the logic behind the scenes to make it easier to inject dependencies. But you can use Dependency Injection in plain JavaScript, and that’s not so hard.
Dependency Injection is not a prerogative of Object-Oriented Programming.
You can also inject dependencies without a constructor. It is possible to use dependencies even without the “this” keyword. There are ways to implement Dependency Injection using Functional Programming.
What is Dependency Injection, then?
What is Dependency Injection
Dependency Injection is where dependencies of a piece of code are supplied to it as parameters rather than create by it.
Dave Farley, Modern Software Engineering
Let me explain this with an example:
Note: I deliberately used process.env since this is not production code, and the post aims to explain a concept. Don’t do that!
The “createUser” function does 3 things:
Accesses the environment variable to take the database URL.
Takes the client from another module and connects it to the database.
Inserts the user.
This approach has at least 2 problems:
Unit testing the function is not easy.
Using the same function and connecting it to a different database is impossible.
How can we fix this?
Functional Dependency Injection
A solution to this issue is to pass the URL as an argument to the function:
This solves the issue of using a different database connection, but testing this function might still be tricky. To test it, it is necessary to create a mock for the client.
To make this function easier to test, you can also consider passing the client as an argument:
Now, writing a test is easier:
Creating the fake client is a bit verbose, but the function is testable as a unit and without using external mocks.
But there’s one thing we should still remember: the function is named “createUser”.
The name of the function is not “connectClientAndCreateUser”. Connecting a client is out of the scope of the function. The only purpose of the function should be to create a user.
The only thing the function needs to create a user is a connected “db” object.
How this db is connected is not a concern for this function. The function should only care about how to use this db for its purpose.
This solution makes the test even simpler:
We can go one step further and separate the dependencies from the arguments of the function using higher-order functions:
In the example above, the createUser function is now a higher-order function returning a function.
This method has 2 advantages:
Conceptually, dependencies and arguments are separated.
It is possible to instantiate the createUser function multiple times with different databases injected.
Improve Dependency Injection With Fastify
You can achieve great modularity using Fastify and the approach explained above.
Fastify has 2 features that you can use to modularize your application and implement Dependency Injection in a clean way:
Here are the steps to follow.
1. Decorate The Fastify Instance With Your Services
This plugin connects a client using a URL received as an option and decorates the Fastify instance with a connected db.
Using this pattern, you should never see this line in other places:
import client from 'client.ts'
2. Transform Your Function Into a Plugin
The createUser function is now used as a factory to create an instance. It only receives the fastify instance as input; what is happening?
To better understand what is going on, let me show you how this plugin could be used:
You can register the plugin whenever you need to use the createUser function, and Fastify will do the rest.
The fastify instance acts like a service container, a concept in many OOP frameworks to support Dependency Injection.
If you want to learn more about Fastify, I suggest you read the book.
Read Next:
Repository: Where It Should Live and How To Avoid Common Mistakes
Creating abstractions to access data in a recurring topic. In the dynamic world of software engineering, especially within the fast-paced startup environment, the ability to write scalable and maintainable code is paramount. As projects become complex, the direct interaction with data storage can quickly become a tangled …
Discovering 2 Fastify Underrated Tricks You Must Know as a Backend Developer
I’ve been using Fastify for a few years. It is my go-to choice for every project. Even if I have some experience with Fastify, sometimes, I come across some functionality I didn’t know. I discovered some of them during a workshop, others reading the documentation, and others reading the book (affiliate