Dependency Injection for Modern Javascript Using ES6 Classes and ES7 Decorators

In the past, I've done a lot of work with PHP and the Laravel framework. One of the coolest features about Laravel is its Inversion of Control system, which dynamically injects dependencies into your application at runtime.

This is beneficial for many reasons, including modularity, readability, and testability. With a dependency injected system, you can simply request that your application receives an instance of an object, and the DI container will do all of the work to initialize the object and all of its dependencies.

Dependency injection is a relatively advanced topic, but the benefits outweigh the cost. For example, consider the following scenario:

class Config {
    public function __construct() {
}

/**
 * Get the configuration item with the specified key
 */
public function get($key) {
    // Get the configuration value with the specified key
    return $value;
}

}

class Database {
/**
* Construct a new database instance using the specified configuration.
*/
public function __construct(Config $config) {
$this->config = $config;
}

public function connect() {
    $host = $this->config->get("db.host");
    $name = $this->config->get("db.name");

    // ... perform the rest of the database initialization
}

}

This is an extremely trivial example, but as you can see, our database object depends on the configuration. If we simply create a single instance of the database, this isn't a problem-- we can simply create a new configuration object and pass it in the constructor. But, what if our application is composed of multiple controllers, utilities, and snippets of code that all need a database instance? Suddenly, we either have to create a large number of database objects (and consequently, a large number of configuration objects), or pass a single instance around somehow.

This is the premise of dependency injection: DI takes the premise of passing instances around, and manages this behavior for you. If we apply our example above to a dependency injected system, we actually don't ever have to explicitly create a configuration object-- our DI system does this for us.

Laravel's IoC system is great-- it's wired up throughout the framework so that most method calls end up having dependencies injected automatically, and it even uses the type hints in the method signature to determine what dependencies the method has.

For example, Laravel's IoC system would use the following method signature to pass in the current HTTP request object and a database instance, automatically:

public function getUsersController(Request $request, Database $db)

The DI pattern doesn't just apply to PHP and Laravel-- Javascript frameworks such as Angular and Aurelia have their own dependency injection systems, with the latter even using ES7-style decorators. Unfortunately, these systems are tightly coupled with the frameworks, meaning that they aren't very useful for developers that want to use them with Node.JS.

Needlepoint: Dependency Injection for Modern Javascript

Needlepoint is a new DI framework for Javascript environments that supports the latest ES6 and ES7 features. Everything works with ES6 classes, and the dependency injection parameters are defined using the decorators ES7 proposal.

Need to learn how to use the new ES6 and ES7 features? Check out my in-development course on Modern JavaScript.

Declaring Dependencies of a Class

There's two key decorators that indicate how a class should be dependency injected. First is the @dependencies decorator:

@dependencies(DependencyOne, DependencyTwo)
export default class ClassWithDependencies {

Simple pass in the classes to the decorator, and the dependencies will be injected into the constructor:

constructor(instanceOfDependencyOne, instanceOfDependencyTwo)

Declaring a Singleton

The second decorator is used to declare a class a singleton:

@singleton
export default class SingletonClass {

The singleton decorator indicates that no more than one instance of the class should be created in the application. If two classes declare a singleton as a dependency, only a single instance will be created which will be injected into the relevant classes that declared the singleton as a dependency.

The best way to introduce the library is to simply illustrate how it works:

/* config.js */

import {singleton} from 'needlepoint';

@singleton
export default class Config {
constructor() {

}

/**
 * Get a configuration value for the specified key
 */
get(key) {
    return this._data[key];
}

}

/* database.js */

import {dependencies, singleton} from 'needlepoint';

@dependencies(Config)
@singleton
export default class Database {
constructor(config) {
this.config = config;

    this.configureDatabase();
}

configureDatabase() {
    // ... configure the database with the current configuration instance
}

query(q) {
    // ... Perform the specified query
}

}

/* index.js */

import {container} from 'needlepoint';

import Database from './database';

var db = container.resolve(Database);
db.query("SELECT * FROM users");

This example is very similar to the PHP example we previously had-- a database needs a configuration object to configure itself. As you can see, in our index.js file we have a single call to container.resolve(Database) which does all of the magic: the database instance is created and passed in an instance of the configuration, with both being singletons. If we were to call container.resolve(Database) again, we would receive the exact same instance that was created the first time.

The same is true for the configuration object-- only a single instance is created for the entire application, so we might add a new "queue" class that uses the same configuration instance:

import {dependencies, singleton} from 'needlepoint';

@dependencies(Config)
@singleton
export default class Queue {
constructor(config) {
this.config = config;

    this.configureQueue();
}

configureQueue() {
    // ... connect to the queue and configure it with the currently
    // initialized configuration instance.
}

next() {
    // ... get the next item from the queue
}

}

Now, we can run container.resolve(Queue) in our index file and get an instance of the queue class. Of course, since this is the first time that the queue is resolved (either explicitly or as a dependency of another object), it is actually instantiated. However, the constructor is passed in the previously created configuration object-- the same exact instance that was passed to the database.

Of course, this also works with more complex dependency graphs. Image having an application with a dependency graph that looks something like this:

Dependency Diagram

Wiring that up manually and keeping track of everything would be a nightmare (and this isn't even close the complexity of a real application), but with Needlepoint you can simply define each class's dependencies with the decorator.

To find out more about to use Needlepoint and dependency injection in your Javascript applications, you can visit the GitHub repository for the project. The entire thing is open source and written using the latest ES6 features, meaning Babel is required for most Javascript environments.

You can use Needlepoint in your Node.JS applications fairly easily. Simply install it with NPM and ensure you have Babel installed and configured:

npm install --save babel needlepoint

And, in your Javascript:

require('babel/register')({
    optional: ['es7.decorators'],
// A lot of NPM modules are precompiled so Babel ignores node_modules/*
// by default, but Needlepoint is NOT pre-compiled so we change the ignore
// rules to ignore everything *except* Needlepoint.
ignore: /node_modules\/(?!needlepoint)/

});

require('app.js');

/* app.js */

import {container} from 'needlepoint';

// Use Needlepoint here

There's still a ton of things left to do, but you can always help out by submitting a pull request or even just filing an issue.

Visit the GitHub page.