Skip to content

English__Theory__Using Environmennt Variables as Data Source

Ramirez Vargas, José Pablo edited this page Nov 7, 2024 · 5 revisions

Using Environment Variables As Data Source

It is common practice to not add certain configuration values to configuration files because configuration files are checked into source control. It is common practice to not add, for example, product keys or account passwords because then anyone with access to source control can obtain said information.

A very common practice is to use environment variables to inject configuration data that cannot be written to configuration files.

wj-config fully supports the use of environment variables as configuration data source. This is done quite simply and all that is needed is to follow a particular naming convention in order to properly position the values in the hierarchical configuration object.

Adding the Environment Variables Data Source

Let's write a quick example:

import wjConfig, { buildEnvironment } from 'wj-config';
import mainConfig from './config.json' assert { type: 'json' };

const myEnvs = [ 'Dev', 'PreProd', 'Prod' ];

const env = buildEnvironment(process.env.NODE_ENV, myEnvs);

const config = await wjConfig()
    .includeEnvironment(env)
    .addObject(mainConfig).name('Main')
    .addPerEnvironment((b, e) => b.addObject(loadJsonFile(`./config.${e}.json`)))
    .addEnvironment(process.env, 'APP_')
    .build();

export default config;

This example builds on the conditional per-environment example discussed here and adds the line .addEnvironment(process.env, 'APP_'). The source is added last, so it means that values coming from environment variables have the highest priority, and in the event of collision, the values defined in environment variables will have precedence over any other values. The example is for a NodeJS application.

What you see in the example is all you need to add to bring environment variables into your final configuration object.

There are, however, 2 conditions that must be met by individual environment variables to qualify:

  1. The environment variable's name must start with the specified prefix, which was set to APP_ in the example.
  2. The environment variable's name must use double underscores (__) to separate the names of the various hierarchical levels, called nodes.

The final property value that is not a node is called leaf.

The first one is, hopefully, pretty straightforward: If you have an environment variable called MYVAR, it won't participate in the configuration building process because its name doesn't start with the specified APP_ prefix. If its name is APP_MYVAR, then it will participate in the configuration building process.

A prefix is always required, and if none is specified, then the default OPT_ is assumed.

The second condition probably requires a bit more explanation, so read the next section to fully understand it.

Specifying the Hierarchy of an Environment Variable

As you know by now, wj-config produces a hierarchical configuration object. This means that the final configuration object contains properties, and said properties may be objects with sub-properties, and so on until the end of time.

This hierarchical nature is not available in system environment variables. The collection of environment variables is a flat collection of key/value pairs. Therefore, in order to allow an environment variable to specify its hierarchical position properly, its name may separate the node names using double underscores.

As example, let's describe a final configuration object:

{
    "app": {
        "title": "My App",
        "version": "v1.0.0"
    },
    "db": {
        "server": "mydbserver.example.com",
        "port": 1433,
        "username": "sys_app",
        "password": "from-environment-please"
    }
}

In our example, we have a section called db that contains database connection information. The database connection cannot be anonymous and therefore requires username and password, but the password cannot be saved into any JSON file due to security concerns, and instead we want it injected via an environment variable. The question is: What must we name this environment variable so the final configuration object has the password in the desired path? The desired path, in case is not clear, is db.password.

Simple: The environment variable's name must be <prefix>db__password, where <prefix> is whatever prefix was specified during configuration construction. Using the example above, the name would then have to be APP_db__password.

There is no limit to the number of node names that the variable can have as long as the host system allows it.

IMPORTANT: JavaScript is a case-sensitive language, so the environment variable's name must match the JavaScript expected name, exactly. Using all-capital names probably won't work.

It is possible to not specify any hierarchy at all, in which case the value will be imported at the root level of the final configuration object. As example, the environment variable APP_test=Hi! will create the test property at the configuration root with the value 'Hi!' (a string value).

Data Type Coercion

The environment variables data source is a specialized dictionary data source. Dictionary data sources undergo data type coercion, and therefore environment variable values do too. Read the details here.