📜 ⬆️ ⬇️

Taming Web Application Configurations with node-convict

From the translator: This is the seventh article in the Node.js series from the Mozilla Identity team that deals with the Persona project.





In this article in the loop on Node.js, we will look at the node-convict module, which helps manage the configurations of Node.js applications. It provides transparent default settings and built-in typing to make it easier to find and fix errors.
')

Formulation of the problem


There are two main problems that create the need for application configuration:


These problems can be solved by initializing some variables depending on the current environment and using environment variables to store sensitive data. A common pattern in Node.js for implementing this approach is to create a module that exports the configuration:

var conf = { //   - // "production", "development",  "test env: process.env.NODE_ENV || "development", // IP  ip: process.env.IP_ADDRESS || "127.0.0.1", //  port: process.env.PORT || 0, //   database: { host: process.env.DB_HOST || "localhost:8091" } }; module.exports = conf; 

This works well, but there are a couple more problems:



Introducing convict


node-convict solves both of these problems by providing a configuration scheme in which you can specify type information, default values, environment variables, and documentation for each of the settings.

Using convict, the example above takes the following form:

 var conf = convict({ env: { doc: "The applicaton environment.", format: ["production", "development", "test"], default: "development", env: "NODE_ENV" }, ip: { doc: "The IP address to bind.", format: "ipaddress", default: "127.0.0.1", env: "IP_ADDRESS" }, port: { doc: "The port to bind.", format: "port", default: 0, env: "PORT" }, database: { host: { default: "localhost:8091", env: "DB_HOST" } } }); conf.validate(); module.exports = conf; 

It contains almost the same information, but presented in the form of a diagram. Due to this, it is more convenient for us to export it and display it in a readable form, to do validation. The declarative format makes the application more reliable and more friendly to all team members.

How is the scheme


For each setting, there are four properties, each of which helps to make the application more reliable and easier to understand:

  1. Type The format property indicates either one of the built-in convict types ( ipaddress , port , int , etc.) or a function for validating user types. If the parameter fails type checking during validation, an error occurs.
  2. Default values Each parameter must have a default value.
  3. Environment variables If the variable specified in env, set, then its value will be used instead of the default value.
  4. Documentation The doc property is quite obvious. The advantage of including the documentation in the schema before comments in the code is that this information is used in the conf.toSchemaString() method for more informative output.

Additional configuration levels


Above the foundation of the default values, you can build additional configuration levels using the conf.load() and conf.loadFile() calls. For example, you can load additional parameters from a JavaScript object for a specific environment:

 var conf = convict({ //   ,      }); if (conf.get('env') === 'production') { //          conf.load({ port: 8080, database: { host: "ec2-117-21-174-242.compute-1.amazonaws.com:8091" } }); } conf.validate(); module.exports = conf; 

Or you can create separate configuration files for each of the environments, and load them with conf.loadFile() :

 conf.loadFile('./config/' + conf.get('env') + '.json'); 

loadFile() can also load several files at once if you pass an array of arguments:

 // CONFIG_FILES=/path/to/production.json,/path/to/secrets.json,/path/to/sitespecific.json conf.loadFile(process.env.CONFIG_FILES.split(',')); 

Loading additional parameters via load() and loadFile() is useful when there are settings for each of the environments that should not be set in the environment variables. Separate declarative configuration files in JSON format allow you to more clearly present the differences between the parameters in different environments. And since files are uploaded using cjson , they can contain comments, which makes them even more understandable.

Note that environment variables have the highest priority, higher than the default settings and settings loaded via load() and loadFile() . To check which settings are valid, you can call conf.toString() .

“V” means validation


After the settings are loaded, you can run a validation to check if they all have the correct format in accordance with the scheme. There are several built-in formats in convict, such as url , ports or ipaddress , and you can also use built-in JavaScript constructors (for example, Number ). If the format property is not specified, convict will check the type of the parameter to match the default value type (by calling Object.prototype.toString.call ). The following three schemes are equivalent:

 var conf1 = convict({ name: { format: String default: 'Brendan' } }); //    , ,     //  ,      var conf2 = convict({ name: { default: 'Brendan' } }); //    var conf3 = convict({ name: 'Brendan' }); 

The format can also be specified as an enumeration, by explicitly specifying a list of acceptable values, for example ["production", "development", "test"] . Any value that is not in the list will not pass validation.

Instead of the built-in types, you can use your own validators. For example, we want the parameter to be a string of 64 hexadecimal digits:

 var check = require('validator').check; var conf = convict({ key: { doc: "API key", format: function (val) { check(val, 'should be a 64 character hex key').regex(/^[a-fA-F0-9]{64}$/); }, default: '3cec609c9bc601c047af917a544645c50caf8cd606806b4e0a23312441014deb' } }); 

A call to conf.validate() return detailed information about each erroneous setting, if any. This helps to avoid redeploying the application when each configuration error is detected. Here's what the error message will look like if we try to set the key parameter from the previous example to the value 'foo' :

 conf.set('key', 'foo'); conf.validate(); // Error: key: should be a 64 character hex key: value was "foo" 

Conclusion


node-convict extends the standard Node.js application configuration template, making it more reliable and convenient for team members who don’t have to figure out the wilds of imperative code to check or change settings. The configuration scheme gives the project team more context for each setting and allows you to do validation for early detection of errors in the configuration.



Source: https://habr.com/ru/post/197166/


All Articles