Dependency Initialization in Express JS

Engineering Sep 08, 2018

What Exactly is Dependency Injection?

Dependency Injection is used to implement Ioc. Dependency injection is a design pattern that is used in creation of dependent objects outside of a class. It is a technique in which an object receives other objects that it depends on. It involves 3 types of classes:

1) Injector Class

2) Service Class

3) Client Class

How to Implement Dependency Injection and Why it Is Important?

If you have ever built a web application using Node JS, you have probably used Express JS in your application. The recommended way to inject dependencies in Express JS is to use res.locals or app.locals.

res.locals are good to hold request specific dependencies while app.locals are good to hold app specific dependencies.

This Helps With a Couple of Things:

  • If we need to change the implementation of a dependency (keeping interface same), we only have to do it in one place (hopefully in app.js) which injects the dependencies.
  • It helps with automated testing as we can easily mock these dependencies.

In one of our applications, we had to wait for n required dependencies to initialize before we can start our server. Our app.js started becoming difficult to maintain as we initialized all dependencies in place.

We Recently Built Our DI Initialization Container to Help With It:

const async = require('async');

const {Logger} = require('./helpers');

function registerEntryWithLoader(loader, entry) {
  loader.push(function (done) {
    const {module, namespace} = this;
    // init module
    module.init((err, instance) => {
      if (err) return done(err);
      return done(null, {instance, namespace});
    });
  }.bind(entry));
}

/**
 * @constructor initializes DI
 * @param entries - modules with config to be registered
 * @param {Function} [done] - optional callback fired when ready
 */
module.exports = (entries, done) => {
  // init loader
  const loader = [];
  // register module with loader
  entries.forEach((entry, i) => {
    // get stuff from entry
    const {module, namespace} = entry;
    // check for init handler, throw error if not present
    if (!module.init) throw new Error(`DI encountered error while registering module [${i}] with loader - missing init handler`);
    if (typeof module.init !== 'function') throw new Error(`DI encountered error while registering module [${i}] with loader - init must be a function`);
    // check for namespace
    if (!namespace) throw new Error(`DI encountered error while registering module [${i}] with loader - missing namespace`);
    if (typeof namespace !== 'string') throw new Error(`DI encountered error while registering module [${i}] with loader - namespace must be a string value`);
    // all good, register with loader
    registerEntryWithLoader(loader, entry);
  });
  // hosting modules for future use
  let _modules;
  // initialize modules via loader
  async.series(loader, (err, iModules) => {
    if (err) throw err;
    _modules = iModules;
    // fire callback if provided
    if (done) done();
  });

  return (req, res, next) => {
    // inject
    _modules.forEach((mod) => {
      res.locals[mod.namespace] = mod.instance;
    });
    // conclude
    next();
  };
};

Here is a sample implementation of a dependency:

const {MongoClient} = require('mongodb');

// public API
let MONGO_DB = null;

exports.init = (done) => {
  MongoClient.connect(process.env.MONGO_DB_URI, (err, db) => {
    if (err) {
      done(err);
    } else {
      MONGO_DB = db;
      done(null, db);
    }
  });
};

exports.getDbRef = () => MONGO_DB;

How to Initiate Dependencies in app.js?

We initiated our dependencies in app.js as follows:

// init dependency injection
// register modules with respective namespace
// module then can be accessible via req.locals.namespace within the controller
app.use(DI([
  {module: mongoClient, namespace: 'Db'}
], () => {
  // fire app.ready
  // do it in next iteration to avoid server from not picking up the event
  process.nextTick(() => app.emit('ready'));
}));

It fires a ready event once all dependencies are initialized on which we started our server:

// server should start listening only when app is ready
app.on('ready', () => {
  server.listen(process.env.PORT);
});

The dependencies can now be accessed by controllers via res.locals[namespace]. In the example above, the mongo db connection handler is accessible via res.locals.Db. The above DI initialization pattern can easily be extended to inject app level dependencies as well.

What’s the biggest thing you’re struggling with right now that we as a technology consulting company can help you with? Feel free to reach out to us at info@jalantechnologies.com. We hope our assistance will help!

Tags

Jaikishan Jalan

12+ years of experience building native apps, websites, backend @microsoft, @google, @startups. ISU PhD dropout, IIT Dhanbad Alum

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.