confmgr

I am happy to announce the release of confmgr. You may visit the repo, directly install its NPM package or follow me for a gentle introduction.


1. The problem

There are several ways to manage various configurations in your NodeJS App. One of them is about using Environment Variables.

Invoking your index.js as follow:

MY_VAR1=42 node index.js

makes MY_VAR1 available to your script with the following code:

const var1 = process.env.MY_VAR1
console.log(`var1=${var1}`)

While this method works perfectly fine, it has a few shortcomings.

First, you start polluting your ENV, like many others, with variables that may already be used by other projects. Take for instance DATABASE, there are few guarantees that it will point to the Database you expect for your application.

Another caveat when using ENV is that everything is a string. There is no such a thing as a number for instance…​ Take the following snippet:

const num = process.env.MY_NUM
const val = num + 1
console.log(val)

Now run it with MY_NUM=42 node test.js

You may be surprised to see that the output will be 421 and not the 43 you may have expected.

Another issue that nothing enforce the user to really pass this value. As a matter of fact, the user of your script can only get support in either reading your code or reading your doc (assuming it is up to date…​).

How will you as a dev, even display the list of supported variables. Sure you may start building an array with those values and track down what they are, and add some code to your App that has nothing to do with your App!

Another issue is formatting, how can we handle what is supposed to be an email address when the user passed http://www.gmail.com. Chances are it will break somewhere…​ Here we go for some more code in our App for something that should be as simple as a config object!


2. The solution

I have released confmgr to address the issues mentioned above.

confmgr is a NPM package that will take all the burden of managing your config away from you and from your App. confmgr is written in Typescript and you may use it in both Typescript and Javascript.

Let`s see how we can use it.


3. Usage

3.1. Install

First we want to install it, I am assuming you already have a Typescript or Javascript App with a package.json. We will add confmgr as dependency. I am using yarn but most commands are very similar to the NPM commands. If you visit the project page, you will also see how to use NPM

yarn add confmgr

Now that we have installed confmgr, we need to create a Config Spec. This is a YAML file that describes what your config is and how it should behave.

3.2. Config Specs

Here is a minimal Config Specs, let’s save it under configSpecs.yml:

TS_SAMPLE:
  MODULE_01:
    PARAM1:
      description: This is my first variable

Simple isn’t it ?

3.3. 2 lines of code

Now lets use it, we need a little piece of code. I will be using Typescript but you can find Javascript examples in the samples folder of the repo.

import { ConfigManager } from 'confmgr'
const config = ConfigManager.getInstance('configSpecs.yml').getConfig()

// ... more to come
Note
You can find the full code for this example here.

We first import the ConfigManager object from confmgr. We then ask it for config object with const config = ConfigManager.getInstance('configSpecs.yml').getConfig()

3.4. Some content

For this code to run, we need a third piece of data: the content! We will be using an .env file:

TS_SAMPLE_MODULE_01_PARAM1=12

That`s all! We now have a config object containing our PARAM1 value. We can now Validate(), Print() our config or use its content:

// ... init code we saw above

// Validate an show whether the whole config is valid
console.log(`Your config is${config.Validate() ? '' : ' NOT'} valid!`)

// Print the config
config.Print()

// Use the config
const param1 = config['TS_SAMPLE_MODULE_01_PARAM1']

Printing your config will look (with a config slightly more elaborated) like:

===> TS_SAMPLE_MODULE_01 ENV:
✅ PARAM1: some param1
    value: 12
✅ PARAM2: some param2
    value: 44
✅ SECRET: some secret
    value: *****                              <---- Notice how this value has been masked
✅ REGEXP: some regexp
    regexp: ^\d{2}_\d{2}
    value: 68_77
✅ MANDAT_WITH_DEF: some optional param
    value: 42
❌ MANDAT_NO_DEF: some optional param         <----- It looks like we forgot to pass a value
    value: undefined
Note
The output is colored by default but does not show above so let me show you how beautiful this is below :)
color invalid

At that point, you may wonder how do some values get magically masked or how can confmgr know that some missing values should be there or some values are incorrect.

This is where the options in our Config Specs come to play. Let’s see a full example:

MYAPP:
  MYMODULE:
    NUM:
      description: some number
      type: number
      default: 42
    STR:
      description: some string
      mandatory: true
      regexp: ^\d{2}_\d{2}
    PASS:
      description: some string
      mandatory: true
      masked: true

The previous Config Spec shows that we expect (maybe…​) the following values:

  • MYAPP_MYMODULE_NUM

  • MYAPP_MYMODULE_STR

  • MYAPP_MYMODULE_PASS

MYAPP_MYMODULE_NUM is a number so confmgr will know it needs to convert '42' into 42 to ensure that we don’t run into the issue we saw at the beginning of this article. This would not even be a big deal if this value is not provided since it is not mandatory and we provide a default value.

MYAPP_MYMODULE_STR however is a string. No conversion will be required. But we tell confmgr that this value MUST be there. Even more than being there, this string must look like 12_34 (2 digits followed by underscore followed by 2 digits, dont’t ask, this is an example…​). This is what the regexp option specifies with ^\d{2}_\d{2}.

MYAPP_MYMODULE_PASS is also mandatory, the content is free but the masked option instructs confmgr to NEVER display this field in the logs.

Warning
Beware, nothing prevents YOU from showing secrets in your logs…​

Talking about logs, you may use a specific logger and probably prefer to use it to show the config? confmgr has you covered. Say your logger function works like winston.debug('Some debug …​'). We can tell confmgr to use winston (or any logger) when calling Print(…​):

// import ...
const config = ConfigManager.getInstance('configSpecs.yml').getConfig()
config.Print({ logger: winston.debug })

That ends our gentle introduction to confmgr. Feel free to stop by https://gitlab.com/chevdor/confmgr to star the project, discover more of the documentation and submit some PRs!


This site is best viewed using the Brave browser.

Not only it will help you block most trackers, keep you safer on the internet but it also allows you supporting websites such as this one without having to spend a dime.

Using Brave allows me bringing this content to you, without any ad, subscription and other annoyance (I am looking at you Medium…​). Using Brave gives you the opportunity to show your appreciation for that in a few clicks!

You can read more about Brave in this article.


Avatar
Wilfried Kopp aka. Chevdor
Building Blockchains & Decentralized Solutions

I build decentralized solutions and tooling to support them. I am developing Smart Contracts and dApps on Ethereum and Substrate (Polkadot & Kusama) while aspiring at becoming more proficient with Rust. I am using Docker extensively and above all I like efficiency. GPG Fingerprint 15AF C574 D3F9 F1C3 CCDD E31E 2DCE C4DC 506E 6475.

Related