config-ninja

Environmental JSON config.

Usage no npm install needed!

<script type="module">
  import configNinja from 'https://cdn.skypack.dev/config-ninja';
</script>

README

Config-Ninja

A quick and easy way to load in config from disk or environment variables. Config-Ninja uses the NODE_ENV environment variable to determine whether your code is running in production mode, staging, or any other environment, otherwise development mode is assumed.

Once your config has been initialised Config-Ninja allows you to const config = require('config-ninja').use('my-config'); in any of your modules and get access to your config object.

Quick Start

Create a directory to hold your config files and create a production.config.json file which will contain all your configuration properties. Then create a development.config.json file which will hold only the specific values that need to be different in your development environment. Then in your application entry point, e.g. index.js:

// Prepare the ninja on application load.
const config = require('config-ninja').init('my-config', '/path/to/cfg/dir/');

To load the config in other modules:

// Load in the config again, taking advantage of Node's module caching.
const config = require('config-ninja').use('my-config');

// Use the config!
console.dir(config);
console.log('Nested Number:', config.nested.number);  // See examples.

Example: See example.js for a working example which you can run with node ./examples/example.

Important Notes

Production Config

The production config is always the default config. If you specify another environment such as staging or development, Config-Ninja will deep merge the properties from that environment into the production config, overwriting any values that already exist. You can nest properties as deeply as you like. Your files are not modified.

Setup your Config Files

You will need at least 2 config files, one for production and one for development. You may also want config files for other environments such as staging. You can have as many files as you need.

/myConfig
  /production.config.json
  /staging.config.json
  /development.config.json
  /custom.config.json

Config ID

The config id you set needs to be unique for your application or module because of the way Node caches modules in memory. It's possible that your module and another dependency will be using the same instance of Config-Ninja.

If that happens and there is a collision of config ids an error will be thrown. One way to avoid this could be to use some information from your package.json to set the config id like this:

const packageJson = require(`./package.json`);
const config = require(`config-ninja`).init(`${packageJson.name}-${packageJson.version}-config`);

Reserved Property Names

Properties in the top level of your config that begin with two underscores (i.e. __reload) are reserved names and should not be used as config properties.

Advanced Usage

Specify Extra Options

You can also specify some options when instantiating Config-Ninja. All options are optional and must be passed as a hash as the last parameter:

const config = require('config-ninja').init('my-config', '/path/to/cfg/dir/', 'development', { ... });
const config = require('config-ninja').init('my-config', { ... });

See the API Overview below for the options you can specify.

Local Config

You may also wish to add local config files that are not committed to your repo but must be present on every developer's machine e.g. local.config.json. Use the ignore rules for your VCS (e.g. .gitignore) to ignore the local files and prevent them from being committed.

By default we assume you might have a local file called local.config.json. You can change this by passing in an array of names in the localConfig option. If you want to throw an error if any of the specified local config files are missing then set the requireLocalConfig option to true.

// Default options for local config.
const config = require('config-ninja').init('my-config', {
  localConfig: ['local'],
  requireLocalConfig: false,
});

Override the Config Directory

By default we assume the config files are located in current working directory + '/config'. You can change this by passing in an absolute or relative path as the second parameter:

const config = require('config-ninja').init('my-config', '/path/to/cfg/dir/');
const config = require('config-ninja').init('my-config', {
  dir: '/path/to/cfg/dir/',
});

Override the Environment

By default production and development environment strings are understood. If you have additional environments you can override the environment string by passing in a third parameter called env, which matches the name of your config file (e.g. staging.config.json):

const config = require('config-ninja').init('my-config', '/path/to/cfg/dir/', 'staging');
const config = require('config-ninja').init('my-config', {
  dir: '/path/to/cfg/dir/',
  env: 'staging',
});

Environment Variables

You can also load config from the environment variables, and optionally from a .env file (see config options below). You'll need to provide a mapping of environment variable names to paths in your config to use this feature. Environment variables overwrite values in your config files even if they are empty strings, but you can avoid this if don't set them at all in the environment.

Note: String representations of true, null false, integers and floats will be converted to their correct data types.

Warning: The NODE_ENV environment variable cannot be loaded from a .env file and will be ignored.

Example .env file:

LOG_LEVEL=verbose
NINJA_AWESOMENESS="very awesome"
OFFSET=5

Example mapping configuration:

{
  environmentVariables: {
    mapping: {
      LOG_LEVEL: `logLevel`,
      NINJA_AWESOMENESS: `how.awesome.are.ninjas`,
      OFFSET: `timezoneOffset`,
    }
  }
}

API Overview

.init(configId[, dir[, env[, options]]])

Sets up a new config object and returns it. Any of the following function signatures are acceptable:

  • .init(configId)
  • .init(configId, dir)
  • .init(configId, dir, env)
  • .init(configId, dir, env, options)
  • .init(configId, dir, options)
  • .init(configId, options)

You can specify the following options. You can either pass dir and env into the function as parameters, or add them as options, or rely on the default values.

Option Default Description
dir ./config Set the directory where your config is stored. Relative paths are relative to the current working directory of your process.
env process.env.NODE_ENV || 'development' Set the environment to a specific environment string (e.g. "production"), defaults to process.env.NODE_ENV or "development".
shortFilenames false Set true if you want to your config filenames to be in the format of development.json instead of the default development.config.json.
environmentLevels { production: 1, staging: 2, development: 3 } If the property env is not already specified in your config files this option will set env.id to the environment string (e.g. "production"), and will set env.level to the corresponding integer specified in this option. Pass in a falsy value to disable this feature.
localConfig[] ['local'] Specify a list of other filenames to merge into your config, if the files don't exist they will just be ignored by default. Properties in local files will overwrite properties with the same name in your config.
requireLocalConfig false By default we don't throw an error if a local config file is missing. Set true to throw an error instead.
environmentVariables.enableDotenv false Set true to load in files from a .env file.
environmentVariables.dotenvPath false Optionally provide a custom absolute path to the .env file.
environmentVariables.mapping Provide a mapping of environment variables to paths in your config file (see the Environment Variables section above).
single Set to a string e.g. "my-settings" if you only want to load a single config file e.g. "my-settings.config.json".
immutable false Set true to force the config objects to always be immutable.
plain false Set true to always construct the config without any of the utility functions attached.

.use(configId[, immutable[, plain]])

Return an existing config object that has been initialised with .init().

You can pass true as the second parameter to return a copy of the config, which prevents accidental changes to the config from propagating through to other modules. This has no affect if the immutable option was passed to .init() as true.

You can also pass true as the third parameter if you wish to return a plain copy of the config without the utility functions attached. This has no affect if the plain option was passed to .init().

.wipe(configId)

This will remove the config from memory, allowing you to re-use an existing config id. This will NOT prevent other parts of your application from continuing to use the config if they have already got a reference to it from .use().

Config Overview

Once you have a config object you can use these utility functions:

config.__inspect()

Returns information about the config object, including some meta data and the options used to initialise the config in .init().

config.__reload()

Reloads the config files from disk, using the original options passed to .init(). Returns the reloaded config, and if the immutable option is false it will also mutate the config object itself.

Warning: This operation is synchronous and blocking.

config.__switch(env)

Reloads the config files from disk and switches to the new environment specified, using the original options passed to .init(). Returns the new config, and if the immutable option is false it will also mutate the config object itself.

Warning: This operation is synchronous and blocking.

config.__addLocal(localConfig)

Reloads the config using the original options passed to .init(), but adds in the extra local config files you specify. Returns the reloaded config, and if the immutable option is false it will also mutate the config object itself.

Warning: This operation is synchronous and blocking.

config.__get(env)

Loads and prepares the config for the specified environment as if we had initialised under than environment, without actually modifying the existing config.

Warning: This operation is synchronous and blocking.

config.__raw(env)

Returns the raw JSON from the config file for the specified environment (from memory). Does not do any merging and does not mutate the existing config.

config.__plain()

Returns a copy of the config without any of the utility functions attached, just the plain properties from your config files.

config.__trace()

Returns a stack trace for when the config was first initialised (or reloaded/switched/etc) as an array of strings. Can be used for debugging to see which module and function instantiated the config object.

FAQ

How can I tell which environment string my config was initialised with?

By default, the env.id property will be set inside your config. If you have manually specified a property called env in your config or the feature has been disabled (by setting the localConfig option) you can use the config.__inspect() method instead. This returns the options that were used to instantiate the config:

const inspection = config.__inspect();
console.log(inspection.options.env);

How can I reload my config?

Simply call config.__reload(). This will reload config files from disk and variables from the environment. See the Config Overview above.

How can I change the environment of my config after initialisation?

Call config.__switch(env). This will reload the config with the new environment set. See the Config Overview above.

How can I load in additional config files from disk after initialisation?

Call config.__addLocal(localConfig) and pass in an array of local config files to load. This will reload the config with the extra config files merged in. See the Config Overview section above.

How can I create a code branch that executes on multiple environments?

See the localConfig initilisation option. If your config files do not include a property called env then Config-Ninja will add in a property with this shape by default:

// THIS
{
  id: "production",
  level: 1,
}

// OR THIS
{
  id: "staging",
  level: 2,
}

// OR THIS
{
  id: "development",
  level: 3,
}

You can use this to create code branches that only execute if the environment is above a certain "level". For example, the following branch will execute if the environment level is higher than 1. By default this will be either the "staging" or "development" environments.

if (config.env.level > 1) { ... }

Can I load config files asynchronously?

No. That's beyond the scope of this module. Config should be loaded when your application first boots, and then only sparingly. This will prevent expensive IO from getting in the way of your application's execution.

Can I load config from a database?

No. That's beyond the scope of this module.