---
title: "IoC in regular JavaScript (ES6+)"
description: "In this post, I will demonstrate the practical application of the [IoC](https://en.wikipedia.org/wiki/Inversionofcontrol) concept in JavaScript code. Unlike languages like PHP, Jav"
date: 2023-07-17
---

In this post, I will demonstrate the practical application of the
[IoC](https://en.wikipedia.org/wiki/Inversion_of_control) concept in
JavaScript code. Unlike languages like PHP, Java, and TypeScript,
JavaScript does not have abstractions (interfaces). Therefore, the
[principle of dependency
inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle)
(“*concrete implementations should depend on abstractions*”) is
difficult for a JavaScript developer to grasp. I hope that by
practically applying the inversion of control in JavaScript, I can help
clarify this issue specifically for JavaScript developers.

Caution: Everything said in this post refers to the ES6+ version, where
the project source code is placed in ES modules and linked together
using
[import/export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).

## Importing as a dependency

First, let’s define what a “*dependency*” is in ES6+, particularly when
considering the situation at the module level. I dare say that any
import (static or dynamic) represents a dependency of our module on some
other module. Thus, in the following code, we can safely say that the
`service` module has at least one dependency — on the `logger` module:

``` js
import logger from './logger.js';
export class Service { /* ... */ }
```

## The forward control

Usually, a service developer needs to know where the dependencies for
his code are. He can follow the link in the import and see the imported
objects (dependencies) — observe the specific implementation of the
dependencies:

``` js
export default {
  error: (msg) => console.error(msg),
  info: (msg) => console.info(msg),
};
```

Imported dependencies can be used inside a module:

``` js
import logger from './logger.js';
export class Service {
  exec() { logger.info('Service is running.'); }
}
```

When we use a service elsewhere:

``` js
import {Service} from './service.js';
const s = new Service();
s.exec({});
```

we also import its dependencies one by one.

With this approach, all project modules end up being rigidly linked with
`imports` into a single hierarchy at the stage of writing the code.

## The inverted control

Let’s imagine a situation where a project can have several loggers:
console, file, network, database, etc. It is possible that one developer
creates a service that uses logging, while another developer creates a
file logger, and a third one makes a network logger. The first thing all
developers will need to do is agree on a logger interface, for example,
like this:

``` js
/** @interface */
class ILogger {
  error(msg) {}
  info(msg) {}
}
```

This code will never be executed by a computer. It uses
[JSDoc](https://jsdoc.app/) solely to document the development process
and to harmonize the interaction between different developers. The file
logger developer writes something like:

``` js
class LoggerFile {
  error(msg) {}
  info(msg) {}
}
```

The network logger developer writes something like:

``` js
class LoggerNet {
  error(msg) {}
  info(msg) {}
}
```

The `service` developer does not know which `logger` will be used at
runtime and must provide the ability to add the required dependency (the
classic way — in the constructor):

``` js
export class Service {
  constructor(logger) { this.logger = logger; }
  exec() { this.logger.info('Service is running.'); }
}
```

This is where the inversion of control comes in. Now, it is not the
`service` itself that decides where it gets the required dependency, but
some external code to the `service` (usually called the object
container). We do not hard-link the project code through imports.
Dependencies can be injected at runtime, flexibly, according to
conditions.

## Conclusion

In principle, in a project with dependency injection, almost the entire
project can be written without using imports at all. Imports will be
needed only in the composition root (the object container), which will
collect all the code together, depending on certain conditions.

The most important condition is that the code of the modules must
provide the ability to inject the necessary dependencies into them from
the outside (through the constructor or the setters). And what is nice
with this approach is that the code becomes loosely coupled. You can
drag and drop a module from one project to another, as long as the
required dependencies are satisfied in the other project as well.

A vivid example of reducing connectivity is the possibility of creating
a test environment for a `service` using TDD:

``` js
import assert from 'node:assert/strict';
import {describe, it} from 'mocha';
import {Service} from './service.js';

const logger = {error(msg) {}, info(msg) {}};
describe('Service', () => {
  it('does the job', () => {
    const service = new Service(logger);
    service.exec();
    assert.ok(true);
  });
});
```

In the test, we mock the dependency of our `service` and define the
behavior we need without referencing a specific implementation (such as
file or network).

In general, inversion of control in application architecture is
primarily about how the individual “*bricks*” for the entire
“*building*” are created, and only then how they are put together. If
the “*bricks*” are made correctly, they can be laid in many ways.

<figure>
<img src="/medium/img/1b2e701f331d/image-01.jpg" alt="Image 2" />
</figure>

I will tell you how to stack “*bricks*” with each other in the following
posts. The purpose of this post is to give an understanding that ES6
modules without static imports may well serve as building blocks for an
application. At the same time, it becomes unimportant what kind of
application it is — browser or Node.js.

If you enjoyed this article, please give it a clap and follow me for
more content!

Stay connected:

- [GitHub](https://github.com/flancer64)
- [LinkedIn](https://www.linkedin.com/in/aleksandrs-gusevs-011ba928/)
- [Upwork](https://www.upwork.com/freelancers/~0181de0a64c6981497)

Thank you for your support!
