Configuration Files
We all know (hopefully) that it's considered harmful to hard-code values in code that might conceivably change at some point. Furthermore, the DRY (Don't Repeat Yourself) principle tells us that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system".
So it's good practice to have a single location where configuration options for your project can be stored. For a smaller projects this might be a single configuration file (e.g. consider how package.json
is used for NPM modules). For larger projects you may choose to have several configuration files to achieve a better separation of concerns and to make them easier to manage.
Config Object
The Config
module simplifies the process of reading configuration files stored in a central location. Tell it where your configuration files are stored and then it will take care of loading them for you.
import { Config } from '@abw/badger'
// look for config files in a `config` directory
const configDir = new Config('config')
// load the badger.(js|mjs|yaml|json) file
configDir
.config('badger')
.then(
config => console.log("loaded the badger config: ", config)
)
Config Directory
When you create a new Config
object you should specify the name of the configuration directory relative to your current location.
const configDir = new Config('config')
You can specify it as a string as shown above, or using a Directory
object as shown in the earlier examples.
For example, if you have a script in the bin
directory and you want to load configuration files from the config
directory located alongside it then you can do something like this:
import { bin, Config } from '@abw/badger'
const configDir = new Config(
bin().parent().dir('config')
)
Or more succinctly like this:
import { bin, Config } from '@abw/badger'
const configDir = new Config(
bin().dir('../config')
)
Multiple Config Directories
If you have multiple locations that you want to read configuration files from then you can specify them as an array. The values of the array can be either Directory objects or strings, or a mixture of the two.
For example if you want to read configuration files from your project root directory and/or a config
directory then you could do this:
import { bin, Config } from '@abw/badger'
const rootDir = bin().parent()
const configDir = new Config(
[rootDir, rootDir.dir('config')]
)
Loading a Config File
The config()
method expects the basename (i.e. no file extension) of a file in your config directory (or one of them).
// load the badger.(js|mjs|yaml|json) file
configDir
.config('badger')
.then(
config => console.log("loaded the badger config: ", config)
)
Javascript Files
It will first look for a Javascript file with a .js
or .mjs
extension. If it finds such a file then it will import it and return a Promise that fulfills with the exports from that file. For example, a config/badger.js
file might look like this:
export const name="Brian";
export const animal="Badger";
YAML or JSON Files
If it doesn't find a Javascript file then it will look for a yaml
or json
file. For example, a config/badger.yaml
might look like this:
name: Brian
animal: Badger
Or a config/badger.json
might look like this:
{
"name": "Brian",
"animal": "Badger"
}
In all the above cases, the same data will be returned in the Promise.
configDir
.config('badger')
.then(
// prints "Brian is a Badger"
config => console.log(config.name, "is a", config.animal)
)
Data Path
If you want to access a particular piece of data from the loaded configuration then you can specify it as a data path fragment following the file name. Separate the file name and data path fragment with a #
.
// load the default export from the badger.(js|mjs) file
configDir
.config('badger#name')
.then(
name => console.log("The badger is called", name)
)
See the Data Paths documentation for further information.
Configuration Options
The jsExt
configuration option can be used to change the file extensions that are recognised for Javascript files (['js', 'mjs']
by default) and the codec
option can be used to specify which codecs can be used for data files (['yaml', 'json']
by default). Note that the names of the codec
correspond to the file extensions, e.g. a file must have a .yaml
extension to be reconised and read using the yaml
codec.
For example, if you only want to look for .js
Javascript files and .json
data files then you would set the options like this:
const configDir = new Config(
rootDir.dir('config'),
{
jsExt: ['js'],
codec: ['json'],
};
)
Or, given that you now only have one value for each of jsExt
and codecs
you could do it like this:
const configDir = new Config(
rootDir.dir('config'),
{
jsExt: 'js',
codec: 'json',
};
)
Benefits
In case it's not immediately obvious, one key benefit of using the Config module to load configuration files is that it allows you to change the format that you're using at any time. You might start off with a simple .json
JSON file then later decide that you want to change to a .yaml
YAML file so that you can add some comments and whitespace to make it more readable. Further down the line you might need to perform some computation and switch it to a .js
Javascript file.
Of course you still need to re-write your configuration file but you don't need to worry about updating any code that's loading it. When you add a badger.yaml
file to the configuration directory it will immediately take precedence over the badger.json
file, or if you add a badger.js
file it will take precedence over both the badger.yaml
and badger.json
files.
This is why you should NOT provide the file extension in the name you pass to the config()
method. Leave it up to the config()
method to work that out for you and do the right thing.