Elixir applications have a configuration file, which is located in the project directory in config/config.exs. Like mix.exs, config.exs is an Elixir code file containing valid Elixir code, but it creates environment-specific configuration settings. This is the equivalent of a config.json file in Node.js or an app.config or web.config file in a C#/.NET project.
The Configuration File
Here's what the config.exs looks like when it is generated. It is mostly comments describing how you can use it. I recommend reading the comments to get an idea of what you can put in the configuration file.
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# third-party users, it should be done in your "mix.exs" file.
# You can configure your application as:
#
# config :dep_project, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:dep_project, :key)
#
# You can also configure a third-party app:
#
# config :logger, level: :info
#
# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env()}.exs"
The configuration file is run before any dependencies are loaded, so you not only have a chance to configure your own application, but you can configure your dependencies, if those dependencies pay attention to configuration. The configuration file is only scoped to the application it belongs to. That means that if you're creating a package that will serve as a dependency, it won't affect any parent configuration, but it will allow you to configure both the application and its dependencies.
A configuration setting is created using the config/2
function in the Mix.Config module. The first parameter is the atom associated with application being configured and the second parameter is a keyword list of settings.
Here's an example.
config :my_application, title: "The Application of Doom"
config :my_application, setting1: "Value1", setting2 : "Value2"
config :dependency, :repository_url, "https://my.repository.com"
- The first line configures a single setting for the current application, which I just made up. It's not a real application. It's associated with the atom
:my_application
. - The second line sets multiple settings by creating a keyword list. It turns out that multiple key-value pairs separated by commas in Elixir will be automatically converted into a keyword list. I didn't know that until I saw this syntax and wondered how that worked.
- The final line configures a
:repository_url
setting for a dependency associated with the atom:dependency
. That dependency has to actually be looking for that setting in order for it to have any effect, but that works because the configuration is run prior to the application or any of its dependencies being initialized.
If settings are specified multiple times, the later settings override the earlier settings.
The Mix.Config module documentation describes the functions that can be used in the configuration.
Importing Configuration Settings
By default, the configuration settings in config.exs are always loaded into every environment. As shown in the comments in the default config.exs file, you can import other configuration files into the main configuration file, which is great for organizing your configuration settings and conditionally including them.
You can import by file name for files that are always imported.
import_config "additional_config.exs"
You can also specify different configurations for different environments. For environmental configurations, it's best to use the Mix.env/0
function to get the current environment, which will return an environmental string like "dev", "test", or "prod". You could create some logic that will determine what is imported when, but if you just have a separate configuration per environment, just import using string interpolation.
import_config "#{Mix.env()}.exs"
That will import "dev.exs", "test.exs", or "prod.exs" depending on the environment. The environmental string returned by Mix.env/0
is determined at compile time by the mix build tool, so the enitre configuration is determined at compile time.
Using a Configuration Setting in your Code
Your code can use Application.get_env/3
to read a configuration setting. It allows you to specify an optional default value (or returns nil
if not specified) when a configuration setting has not been set.
For example:
title = Application.get_env(:my_application, :title, "Default Title")
This will return the configuration setting :title
associated with :my_application
. If it doesn't exist, then "Default Title" will be used as the title.
You can retrieve configuration settings and store them as a module attribute. Module attribute values are compiled into the compiled code, so if you do refer to a configuration setting in a module attribute, it will be retrieved at compile time rather than runtime.
defmodule ExampleModule
@title Application.get_env(:my_application, :title)
end
Compile Time Configuration
As far as I'm able to tell, the configuration is entirely determined at compile time. This is a bit different from what I'm used to where the configuration is read by the application at runtime. It seems to me that having to rebuild to make a configuration change would be very inconvenient.
I still have much to learn about building and deploying Elixir applications, so perhaps there is also runtime configuration functionality available. I have a hard time imagining that every Elixir application (particularly web applications) cannot be configured at runtime, so I'm hoping I'll eventually learn about runtime configuration.