NodeJS Module Exports: Export multiple functions vs Exporting a single function
Which one is a better approach to setting up variables in NodeJS? (faster, easier to maintain, not an antipattern)


Evaluating the merit of different options depends upon these factors:


How many total options are there now?
How likely are you to add more options as time goes?
Does the caller always set all the options at once? Or is there value to being able to set just some options
Does the caller usually just set one option?


Evaluating each of these:

Total number of options

First off, if you have lots of variables, then neither A or B are particularly practical.  A is not very practical because you have to make a separate API for every variable when they're all really just doing the same thing, but for a slightly different property.  That's generally not a good thing in an API design.

For lots of variables, option B ends up having to list out a lot of different values all in some precise order and that's not easy to use without having the doc page open every time you want to use it and it's not all that easy to read code that's written to use it either because the reader won't remember which position every single parameter is.

Extending to add more options in the future

Both A and B require modifying the API to extend and add more options.  Option A requires adding and documenting a new API entry point.  Option B requires adding and documenting a parameter and then making sure that prior code that doesn't include the new parameter isn't broken (by detecting the missing parameter on the receiving end of things).  Neither is ideal.

Does the caller always set all options at once

If so, option A is a pain.

Does the caller usually set just one option or a small number of options

If so, option B is a pain.

Do, what you find is that neither option A or option B is entirely flexible for different types of calling usage or extensibility or use with lots of options.

Option C

So, I'll propose using a single options object to be a lot more flexible for different types of calling usage and infinite extensibility without changing the API surface itself.  This is also trivial to maintain.  Let's call this option C.

// default values for options
let defaultOptions = {
    optionA: defaultValA, 
    optionB: defaultValB, 
    optionC: defaultValC, 
    optionD: defaultValD, 
    optionE: defaultValE 
};

module.exports.setOptions = function(options) {
    // copy any properties from the options object to the default options
    //   overwriting default values with new values
    Object.assign(defaultOptions, options)
}


Or, in some cases, you want to prevent adding anything to defaultOptions that isn't already there (no additional properties allowed):

module.exports.setOptions = function(options) {
    // Copy any properties from the options object to the default options
    //   overwriting default values with new values
    // Only allow copying of properties that are already in defaultOptions
    for (let prop of Object.keys(options)) {
       if (Object.hasOwnProperty(defaultOptions, prop)) {
           defaultOptions[prop] = options[prop];
       }
    }
}


And, the caller would do something like this:

m.setOptions({
    optionC: "someNewValue",
    optionE: "anotherNewValue"
});


Where they can include only whatever options they want to set and they are named options so even if there were 1000 options and you just wanted to set 2 of them, it works quite cleanly.

For extensibility, option C wins every time because it can be extended by simply documenting that you now accept another property name without changing the surface of the API at all.



Option D

A fourth option which I will call option D, is to just have one "setter" function that takes a property name and a value.

// default values for options
let defaultOptions = {
    optionA: defaultValA, 
    optionB: defaultValB, 
    optionC: defaultValC, 
    optionD: defaultValD, 
    optionE: defaultValE 
};

module.exports.setProp = function(propName, value) {
    defaultOptions[propName] = value;
    return this;
}


This allows you to have one single function that could even be chainable that can set any property.

m.setProp("optionC", "someNewValue").setProp("optionE", "anotherNewValue");


This is probably better than option A if everything is just property name and value because it's more extensible without modifying the API, but it's a pain to use if you're trying to set a lot of properties (where option C excels).



Review

For option A, it's really only practical or best if you have a small number of options (say less than 5) and the caller is typically not setting multiple options (typically only setting one option).  Even then, I probably still prefer option C as its infinitely extensible without adding more entry points to the API.



For option B, it's really only practical or best if you have a small number of options (say less than 5) and the caller is always setting all the options (or at most leaving off the last one or two).  Option B is complicated to use and read if there are lots of options (because you have to remember the right position in the parameter list for each option) and it's complicated to overload to only pass some options.  The best part of option B is that with a small number of options, it's probably the least typing for the caller, but that lesser typing means it's less self documenting than the object with named properties in option C.



For option C, it's infinitely extensible and flexible for any type of caller use from setting one property to setting hundreds of properties.  And, it's trivial to maintain, even as you add more options.  This is generally my preferred choice anytime I have 3 or more options or value extensibility or flexibility in how it's used by the caller.



For option D, I don't personally find it cleaner than option C, but it is flexible and extensible, though inefficient for setting a bunch of options.