Tutorial: Configuration Files

Configuration Files

Flitter's configuration system is in two tiers: environment variables, and configuration files (aka "configs"). All configuration values that are referenced in your application are defined in configuration files in the configs/ directory. Config values that are environment-dependent are additionally defined in references in the .env environment variables file and are loaded in when the application starts.

Creating New Configs

Configuration files are defined in the configs/ directory and can be automatically generated with the Flitter CLI, however they are simply files that export a single object with key-value properties.

./flitter new config new:example

This creates the configs/new/example.config.js:

// config/new/example.config.js
const example = {

}

module.exports = exports = example

You can add configuration values to these files in the way you'd expect:

// config/new/example.config.js
const example = {
	example_title: 'Config Example',
}

module.exports = exports = example

The general philosophy in Flitter is that configuration values should always be static, never computed. If the value is not a reference to an environment variable, it should be some primitive type that doesn't change.

Environment Variables

Flitter has a built-in mechanism for customizing your application's config based on the environment. This is done by setting application specific environment variables in the .env file. This is read in using the dotenv package under-the-hood. Then, references to environment variables are substituted in the configuration files.

The env() Method

Environment variables should be referenced from within configuration files only. If you need to reference an environment variable in the rest of the application, it should be done by accessing a config value.

You can reference environment variables in the config files using the global env() method. This method is made global only within the configuration files while they are loaded. In this sense, it "exists" only within these config files. For example:

// config/new/example.config.js
const example = {
	example_title: env('EXAMPLE_TITLE'),
}

module.exports = exports = config

To set this value, you can set the CONFIG_LOGIN_PAGE_TITLE variable in .env:

EXAMPLE_TITLE="Config Example"
Default Values

You can specify default values for config keys that depend on environment variables. This value will be used if the environment variable is not set:

// config/new/example.config.js
const example = {
	example_title: env('EXAMPLE_TITLE', 'Config Example')
}

module.exports = exports = example
Type Inference

When reading values from the .env file, the type of the environment variables is inferred from the value using module:libflitter/utility/UtilityUnit~UtilityUnit#infer. That allows boolean/number/&c to be interpreted properly (assuming these have been added to the file):

TEST_1="Hello, there!"
TEST_2=Hi
TEST_3=3.141
TEST_4=false
TEST_5=False
> _services.configs.get('new:example.test1') // "Hello, There!"
> _services.configs.get('new:example.test2') // "Hi"
> _services.configs.get('new:example.test3') // 3.141
> _services.configs.get('new:example.test4') // false
> _services.configs.get('new:example.test5') // "False"

Accessing Configs

You should not access configs by requiring the file (because the env() global is no longer available). Instead, you should use the canonical configs service to access configuration files. (See: module:libflitter/config/ConfigUnit~ConfigUnit#get)

To do this, access the get() method and pass in the unqualified canonical path to the config. In the case of our example config, we specify the example file in the new subdirectory, and then keys within that file:

> _services.configs.get('new:example.example_title') // "Config Example"
// Example: app/controllers/Home.controller.js
const Controller = require('libflitter/controller/Controller')

class Home extends Controller {
    static get services() {
        return [...super.services, 'configs'] // Require the configs service to be injected
    }

    welcome(req, res){

    	// "this.configs" refers to an instance of the libflitter/config/ConfigUnit service
        return res.page('welcome', { title: this.configs.get('new:example.example_title') })
        // in the view, the value of title is "Config Example"
    }
}

module.exports = Home

Can I change configs at runtime?

No. Flitter adopts a policy of immutable config values during runtime. While it is technically possible to dig into the configuration service and modify the config values, if you need to do this there's probably a better pattern you should be using. If you need to frequently update a value, consider using the provided Settings model, or use a runtime service.

TODO
- add link to Settings model documentation
- add link to service creation documentation