Security News
pnpm 10.0.0 Blocks Lifecycle Scripts by Default
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
async-config
Advanced tools
This module provides a simple asynchronous API for loading environment-specific config files.
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.
npm install async-config --save
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.
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');
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 argumentsFor 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 argumentsThe 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']
});
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.
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
.
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
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:
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
Resolves a relative path to an absolute path based on the directory containing the configuration file.
Resolves a value to the exports of an installed Node.js module.
For example:
{
"main": "require:./app-production"
}
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
}
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.
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:
String
file path, an async Function
or an Object
.process.env.NODE_ENV
or development
).Array
of sources to exclude. Possible values are the following:
"command-line"
- ignore the --NODE_CONFIG
command line argument"env"
- ignore the NODE_CONFIG
environment variable"env-file"
- ignore path/{name}-{environment}.json
"local-file"
- ignore path/{name}-local.json
Function
with signature function (config)
that can be used to post-process the final configuration object and possibly return an entirely new configuration object.false
then no helpers will be added to the configuration object (currently the get()
method is the only helper added to the final configuration object). The default value is true
.String
file path, an async Function
or an Object
.function
(see the shortstop docs for more details).Pull requests, bug reports and feature requests welcome. To run tests:
npm install
npm test
ISC
FAQs
This module provides a simple asynchronous API for loading environment-specific config files.
The npm package async-config receives a total of 24 weekly downloads. As such, async-config popularity was classified as not popular.
We found that async-config demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
pnpm 10 blocks lifecycle scripts by default to improve security, addressing supply chain attack risks but sparking debate over compatibility and workflow changes.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.