Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NodeJS Module with config - instantiate from require or from module.exports

I'm developing a node.js module that has several functions.
The module itself will require some config to start (there will be defaults... that can be overridden - file paths for example)

What's the preferred approach when exporting a function? Allowing it to be instantiated from a require statement, and pass in the config object (optional) to the constructor?

var MyModule = require('myModule);

var myModule = new MyModule({
    name: "test name"
});

myModule.doThing(function(x){

});

Having this in the module:

module.exports = function MyModule(){
        MyModule.prototype.config = {name: "default name"}

            function doThing(cb){
                //do stuff
                cb();
            }
}

Or, creating an instance from the exports statement in the module, then using it like this:

var myModule = require('myModule);

myModule.config = {name: "test name"}

myModule.doThing(function(x){

});

Instead, having this in the module:

module.exports = exports = MyModule();

function MyModule(){
MyModule.prototype.config = {name: "default name"}

    function doThing(cb){
        //do stuff
        cb();
    }
}
like image 660
Alex Avatar asked Apr 20 '26 11:04

Alex


1 Answers

I’ll say let you users provide an object when instanciating, put the defaults in the prototype. You may also want to provide a configure function, this allows you (now or in the future) to validate keys/values, and your users to set configs after instanciation.

// Module

var Mod = module.exports = function Mod (opts) {
  opts = (opts === Object(opts)) ? opts :  {};

  // This allows users to instanciate without the `new` keyword
  if (! (this instanceof Mod)) {
    return new Mod(opts);
  }

  // Copy user provided configs to this.config
  for (var key in opts) if ({}.hasOwnProperty.call(opts, key)) {
    this.config[key] = opts[key];
  }
};

Mod.prototype.config = {
  foo : 'foo',
  bar : 'bar'
};

Mod.prototype.configure = function configure (key, val) {
  this.config[key] = val;
  return this;
};

// Usage

const Mod = require('/path/to/mod');

var i1 = new Mod;

var i2 = Mod();

var i3 = new Mod({
  foo: 'bar'
});

var i4 = Mod({
  foo: 'bar'
});

i4.configure('bar', 'baz');

var i5 = (new Mod).configure('bar', 'baz');

EDIT

As Jake Sellers noted in the comments, this isn’t standard API pattern in CommonJS modules. A better solution would be to export a function that returns whatever object you’re creating.

More importantly, I should not advise to put the config in the prototype, ever. Doing this makes the config object shared by all children. As such any modification on it will also affect all children. Shame on me, I wasn’t a beginner when I wrote this crap. Double thanks Jake ;)

A better implementation:

// Keep defaults private
var defaults = {
  foo : 'foo',
  bar : 'bar'
};

// Construct
var Mod = function Mod (opts) {
  opts = (opts === Object(opts)) ? opts :  {};

  // This allows users to instanciate without the `new` keyword
  if (! (this instanceof Mod)) {
    return new Mod(opts);
  }

  this.config = {};
  // Copy user provided configs to this.config or set to default
  for (var key in defaults) if (defaults.hasOwnProperty(key)) {
    if ({}.hasOwnProperty.call(opts, key)) {
      this.config[key] = opts[key];
    }
    else {
      this.config[key] = defaults[key];
    }
  }
};

// Let the user update configuration post-instanciation
Mod.prototype.configure = function configure (key, val) {
  this.config[key] = val;
  return this;
};

// Export a function that creates the object
exports.createMod = function createMod (opts) {
  return new Mod(opts);
};

// Export the constructor so user is able to derive from it
// or check instanceof
exports.Mod = Mod;

// USAGE

var mod = require('/path/to/mod');

var i1 = mod.createMod({ foo : 'bar' });

i1.configure('bar', 'baz');
like image 91
kevin Avatar answered Apr 22 '26 02:04

kevin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!