npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

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

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:

  1. path/{name}.json
  2. path/{name}-{environment}.json
  3. path/{name}-local.json
  4. NODE_CONFIG='{...}' environment variable
  5. --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:

  1. path/config.json
  2. path/config-production.json
  3. path/config-local.json
  4. NODE_CONFIG environment variable
  5. --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:

  1. ./raptor-optimizer.json
  2. ./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 async Function or an Object.
  • environment: The value of the environment variable (defaults to process.env.NODE_ENV or development).
  • excludes: An 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
  • finalize: An asynchronous Function with signature function (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 the get() method is the only helper added to the final configuration object). The default value is true.
  • overrides: An array of sources that will be appended to the load order. Each source can be either a String file path, an async Function or an Object.
  • 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

Contribute

Pull requests, bug reports and feature requests welcome. To run tests:

npm install
npm test

License

ISC