async-config
v2.0.0
Published
This module provides a simple asynchronous API for loading environment-specific config files.
Downloads
2,973
Readme
async-config
This module provides a simple asynchronous API for loading environment-specific config files and configuration data from other sources. This module utilizes the shortstop module to provide support for resolving values inside the configuration files based on user-provided "protocol handlers".
This module has extensive tests and is documented, stable and production-ready.
Table of Contents
- async-config
- Installation
- Overview
- Example
- Load Order
- Merging Configurations
- Environment Variables
- Protocol Handlers
- Command Line Arguments
- App/Server Startup
- API
- Notes
- TODO
- Maintainers
- Contribute
- License
Installation
npm install async-config --save
Overview
This module provides a simple API for loading environment-specific configuration files that is more flexible than alternatives. Its design was inspired by config and confit, but there are important differences. Unlike with the config module, this module does not use the exports of the module to maintain the loaded configuration. In addition, this module allows configuration files placed anywhere on disk to be loaded. Finally, this module provides an asynchronous API that allows configuration data to be loaded from remote sources and it avoids the use of synchronous I/O methods for reading files from disk. Compared to confit, this module is more flexible in how configuration files are named and located on disk.
Example
The basic usage is shown below:
Given the following directory structure:
.
└── config
├── config.json
├── config-production.json
└── config-development.json
Each configuration file should contain valid JSON data such as the following:
{
"foo": "bar",
"complex": {
"hello": "world"
}
}
The following JavaScript code can be used to load the JSON configuration files and flatten them into a single configuration object:
const asyncConfig = require('async-config');
const config = await asyncConfig.load('config/config.json');
// The config is just a JavaScript object:
const foo = config.foo;
const hello = config.complex.hello;
// Use the get() method to safely access nested properties:
const missing = config.get('complex.invalid.hello');
Load Order
When loading a configuration file, the following is the default order that configuration data is loaded:
path/{name}.json
path/{name}-{environment}.json
path/{name}-local.json
NODE_CONFIG='{...}'
environment variable--NODE_CONFIG='{...}'
command-line arguments
For example, given the following input of "config/config.json"
and a value of production
, the configuration data will be loaded and merged in the following order:
path/config.json
path/config-production.json
path/config-local.json
NODE_CONFIG
environment variable--NODE_CONFIG='{...}'
command-line arguments
The load order can be modified using any of the following approaches:
const asyncConfig = require('async-config');
const config = await asyncConfig.load('config/config.json', {
sources (sources) {
// Add defaults to the beginning:
sources.unshift('config/custom-detaults.json')
// Add overrides to the end:
sources.push('config/custom-overrides.json')
// You can also push an object instead of a path to a configuration file:
sources.push({ foo: 'bar' })
// You can also push a function that will asynchronously load additional config data:
sources.push(function () {
return Promise.resolve({ hello: 'world' });
});
},
defaults: ['config/more-detaults.json'],
overrides: ['config/more-overrides.json']
});
Merging Configurations
A deep merge of objects is used to merge configuration objects. Properties in configuration objects loaded and merged later will overwrite properties of configuration objects loaded earlier. Only properties of complex objects that are not Array
instances are merged.
Environment Variables
NODE_ENV
By default, the environment name will be based on the NODE_ENV
environment variable. In addition, short environment names will be normalized such that prod
becomes production
and dev
becomes development
.
NODE_CONFIG
This environment variable allows you to override any configuration from the command line or shell environment. The NODE_CONFIG
environment variable must be a JSON formatted string. Any configurations contained in this will override the configurations found and merged from the config files.
Example:
env NODE_CONFIG='{"foo":"bar"}' node server.js
Protocol Handlers
This module supports using protocols inside configuration files. For example, given the following JSON configuration file:
{
"outputDir": "path:../build"
}
Protocol handlers can be registered as shown in the following sample code:
const path = require('path');
const asyncConfig = require('async-config');
const configDir = path.resolve(__dirname, 'config');
const configPath = path.join(configDir, 'config.json');
const config = await asyncConfig.load(configPath, {
protocols: {
path (path) {
return path.resolve(configDir, path);
}
}
});
Since the path
protocol handler is used, the final value of the "outputDir"
directory will be resolved to the full system path relative to the directory containing the configuration file. For example:
{
"outputDir": "/development/my-app/build"
}
By default, the following protocol handlers are registered:
import
Loads another configuration given by a path relative to the current directory.
For example:
{
"raptor-optimizer": "import:./raptor-optimizer.json"
}
Since the async-config
module is used to load the imported configuration file, the imported configuration file will also support environment-specific configuration files. For example:
./raptor-optimizer.json
./raptor-optimizer-production.json
path
Resolves a relative path to an absolute path based on the directory containing the configuration file.
require
Resolves a value to the exports of an installed Node.js module.
For example:
{
"main": "require:./app-production"
}
Command Line Arguments
By default, this module will merge configuration data from the --NODE_CONFIG='{...}'
argument, but you can also easily merge in your own parsed command line arguments. For example, this module can be combined with the raptor-args module as shown in the following sample code:
const asyncConfig = require('async-config');
const commandLineArgs = require('raptor-args').createParser({
'--foo -f': 'boolean',
'--bar -b': 'string'
})
.parse();
(async function () {
const config = await asyncConfig.load('config/config.json', {
overrides: [commandLineArgs]
});
})();
Therefore, if your app is invoked using node myapp.js --foo -b hello
, then the final configuration would be:
{
"foo": true,
"bar": "hello",
... // Other properties from the config.json files
}
App/Server Startup
It is common practice to load the configuration at startup and to delay listening on an HTTP port until the configuration is fully loaded. After the configuration has been loaded, the rest of the application should be able access the configuration synchronously. To support this pattern it is recommended to create a config.js
module in your application as shown below:
config.js:
const asyncConfig = require('async-config');
let loadedConfig = null;
function configureApp (config) {
// Apply the configuration to the application...
// Make sure to invoke the promise when the application is fully configured
return Promise.resolve();
}
exports.load = function () {
// Initiate the loading of the config
loadedConfig = await asyncConfig.load('config/config.json', {
finalize: configureApp
});
return loadedConfig;
};
/**
* Synchronous API to return the loaded configuration:
*/
exports.get = function() {
if (!loadedConfig) {
throw new Error('Configuration has not been fully loaded!');
}
return loadedConfig;
}
If you are building a server app, your server.js
might look like the following:
const express = require('express');
const config = require('./config');
(async function () {
// Asynchronously load environment-specific configuration data before starting the server
const loadedConfig = await config.load();
const app = express();
const port = loadedConfig.port;
// Configure the Express server app...
app.listen(port, function() {
console.log('Listening on port', port);
});
})();
For a working sample server application that utilizes this module, please see the source code for the raptor-samples/weather app.
API
load(path[, options]) : Promise
The load()
method is used to initiate the asynchronous loading of a configuration. The following method signatures are supported:
load(path) : Promise
load(path, options) : Promise
The path should be a file system path to a configuration file. If the path does not have an extension then the .json
file extension is assumed. The input path will be used to build the search path.
The options
argument supports the following properties:
- defaults: An array of sources that will be prepended to the load order. Each source can be either a
String
file path, an asyncFunction
or anObject
. - environment: The value of the environment variable (defaults to
process.env.NODE_ENV
ordevelopment
). - excludes: An
Array
of sources to exclude. Possible values are the following:"command-line"
- ignore the--NODE_CONFIG
command line argument"env"
- ignore theNODE_CONFIG
environment variable"env-file"
- ignorepath/{name}-{environment}.json
"local-file"
- ignorepath/{name}-local.json
- finalize: An asynchronous
Function
with signaturefunction (config)
that can be used to post-process the final configuration object and possibly return an entirely new configuration object. - helpersEnabled: If set to
false
then no helpers will be added to the configuration object (currently theget()
method is the only helper added to the final configuration object). The default value istrue
. - overrides: An array of sources that will be appended to the load order. Each source can be either a
String
file path, an asyncFunction
or anObject
. - protocols: An object where each name is the protocol name and the value is a resolver
function
(see the shortstop docs for more details). - sources: A function that can be used to modify the default load order.
Notes
- Loaded config objects are not cached by this module.
TODO
- Add support for YAML and other configuration file formats?
Maintainers
- Patrick Steele-Idem (Github: @patrick-steele-idem) (Twitter: @psteeleidem)
Contribute
Pull requests, bug reports and feature requests welcome. To run tests:
npm install
npm test
License
ISC