DEV Community

Stefanos Kouroupis
Stefanos Kouroupis

Posted on

NodeJs Singleton Injector

I am going to talk about one of my most used patterns.

When given the task to write an API using NodeJs and Express I like to implement the following pattern, which I tend to call Singleton Injector (yes I like to make up names) that allows me to manage singletons easily.

This pattern is ofcourse the singleton pattern with a small mixture of (but not quite) the dependency injection pattern.

My application entry point looks as follows

(async () => {
    await addAndInitDepedencies();
    // express stuff. configure and start server
})();

async function addAndInitDepedencies() {
    injector.addService(MySql, DatabaseConnection.MySql);
    const mysql = injector.getService<MySql>(MySql);
    mysql.init();

    // other singletons that follow the same pattern
}
Enter fullscreen mode Exit fullscreen mode

So when I need to use for example the mysql connection pool, I can simply do

 this.mysql = Injector.getInstance().getService<MySql>(MySql);
 this.mysql.executeQuery(sql, []);
Enter fullscreen mode Exit fullscreen mode

and this infamous Singleton Injector is just the following really simple class

export class Injector {
    private static instance: Injector;
    private _di: any = {};

    private constructor() { }

    public static getInstance() {
        if (!Injector.instance) {
            Injector.instance = new Injector();
        }
        return Injector.instance;
    }

    public addService(dependency, args) {
        if (this.serviceExists(dependency.name)) { return; }
        const construction = new dependency.prototype.constructor(args);
        this._di[dependency.name] = construction;
    }

    public getInjector() {
        return this._di;
    }

    public getService<T>(dependency): T {
        if (!this.serviceExists(dependency.name)) { return {} as T; }
        const di: any = this._di as T;
        return this._di[dependency.name];
    }

    private serviceExists(name: string) {
        return this._di[name] ? true : false;
    }
}

Enter fullscreen mode Exit fullscreen mode

basically you provide the class you wish to use as well as any arguments that might need, it calls its constructor and returns back the initialized object. And you can get any singleton back anytime by using the class as a type and parameter.

Nothing fancy, just small stuff that makes your life easier.

To be completely honest, until two years ago, I only used Node to write command line tools (so much better than bash), as my background was mainly in c# and c++. It's been an interested trip.

Top comments (2)

Collapse
 
anduser96 profile image
Andrei Gatej

Interesting!

You can also get a singleton just by exporting a new instance of a class from a file.

Say you want to have a singleton of a DB class, in your db/index.js you would have the following:

class Database {
  static connection;
  /* ... */
 }

export default new Database(dbConfig);

And now you could import this in multiple files and you will get the same instance.

This is possible due to how modules are designed to work in node. Long story short, the modules are cached.

Collapse
 
elasticrash profile image
Stefanos Kouroupis

Yes I've seen it been done like that before. But I always found it a bit awkward (syntactically). I ll be the first to admit that this idea in some respects it's equally awkward...but why not.

I choose to do it like that because we usually have lots of things to manage in a single service.

A typical service will have the following

Rabbitmq
Database
Vault
Elasticsearch