Dependency initialization in Express JS

If you are unfamiliar with Dependency injection or why it’s important, please see here.

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:

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

In one of our application, 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;

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, 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.

Logging in NodeJS using Papertrail

 

Recently, I was implementing a 3rd party service in our project and was facing some issue in implementing, I wanted to check what’s missing, what data is being sent and received.

console.log() somehow helps in this, but it’s not a good solution for production, it becomes difficult if you want to see the last week logs, also I wanted handle application errors too. I needed a good log management that helps me in solving these issues.

I was looking for a logger that shows all my requests at a single place and also give alerts whenever an error occurred in my application, so after spending some time on the internet, I found Papertrail very useful for my need.
So along with log management, Papertrail provides a lot of other useful tools like text log, Real-time events, Alerts etc.

We’ll now integrate Papertrail to our NodeJS Application, So let’s get started.

Step 1

First, we need to install the required  modules
npm install winston express-winston winston-papertrail —- save

winston is a logging library with support for multiple transports, in winstona transport is essentially a storage device for your logs, you can read more about it here
express-winston is a middleware for request and error logging of your ExpressJS application
winston-papertrail is Papertrail transport for winston

Step 2

So, After installing modules, create a file logger.js and paste this code

Step 3

Now in your app.js file, use the following code. The expressWinston will add our logger as a middleware, after this, you can see all your request in Papertrail account

Here’s an example of how requests are shown in Papertrail

also if you want to use this as the alternative of console.log, you can you this

logger.log(‘info’,’some demo message’);

Step 6

Now we will create a Papertrail Alert which will notify us whenever an error occurred, we will be receiving notification in our slack channel, also there are other available options which you can use

  1. Enter a search string in the search bar, we are looking for errors only, so will write error and hit enter, you can see all errors now (if any)
  2. Now hit Create Alert button which is at bottom right side
  3. Now you will see lots of integration methods email etc, we will choose slack for now
  4. On next page, add details like how you want error reports and at the bottom add slack Integration’s Webhook URL (you can create a new integration to Papertrail and obtain a new webhook URL )
  5. And hit Create Alert, Now you will be receiving Error notifications to your slack whenever an error has occurred

 

I hope this approach helps you to integrate log management into your app. Please leave comments or suggestions for improvements!