I’m currently working on a small node.js project using the MEAN stack, which means it utilizes mongoDB, Express.js, AngularJS and Node.js. What I like about AngularJS is its dependency injection capabilities. While I don’t think it is perfect (or even close), it does work reasonably well.
Working with AngularJS got me thinking: Why am I not using dependency injection in node.js? What would it even look like? Using node.js, you can just require whatever you need, however, that is not the same as managing dependencies, not even when you overwrite it to provide a different resource.
After a short search, I didn’t find any package that I really liked and started to build pioc. It uses a similar syntax to AngularJS to define required dependencies, however, it supports module inheritance and cleanly separates service definition and service resolution, which allows services to be singletons, unless they need to be reinstantiated.
pioc is quite smart about instantiating services. It will only do that, when it needs to. However, if a child module redefines some dependency of the service you requested, then a new instance will be created with the new dependency (the old one will be used where possible though).
// inside lib/db.js
var MongoClient = require("mongodb").MongoClient;
// we can write modules that require dependencies by specifying
// module.exports as a function
// This one needs a Promise constructor and the configuration for our app
module.exports = function(Promise, config) {
// you like Promises, don't you?
return Promise(function(resolve, reject) {
MongoClient.connect(
config.db.url,
function(err, db) {
if (err) return reject(err);
resolve(db);
}
);
});
};
// inside config/development.json
{
db: {
url: "mongodb://localhost:27017/myproject"
}
};
// inside app.js
var pioc = require("pioc"),
// create a new module,
// by specifying __dirname, we can load services using
// node.js require relative to the app.js file
module = pioc
.createModule(__dirname)
// not stricly necessary to bind bluebird as a Promise service,
// but this offers us the option to change to a different implementation
// easily
.value("Promise", require("bluebird"))
// load the config/development.json file and store it as a value service.
.loadValue("config/development")
// and now load our lib/db.js
.load("lib/db"),
// a module is not able to do anything on its own, most often, you'll
// want an injector or provider to go with it
injector = pioc.createInjector(module);
// Now let's resolve some dependencies
injector.resolve(function(db) {
db.then(function(db) {
console.log("successfully connected to the database!");
db.close();
}).catch(function(err) {
console.log(err);
});
});
For a full introduction and the API documentation, check it out at npm or GitHub.