Often times applications require configuration values which don’t belong in the app or version control. Keeping config separate from code is one of the principles of a 12-Factor app. When writing Ruby/Rails I tend to reach for the dotenv gem and not give this much more thought.
An Elm app I’ve been working on makes HTTP requests to an external API. I wanted to convert these from anonymous requests to sending an API key each time. The API key was a clear case of configuration that I wanted to handle in the environment, but I hadn’t done this with Elm before.
Configure webpack
The app is built and configured with webpack. There is no server-side component. So the first step is to get webpack to pick up the config we need from the environment.
We’ll use the dotenv npm package to read from a local .env
file
(remembering to add .env
to our .gitignore
):
npm install dotenv --save
Then require and configure dotenv in webpack.config.js
:
require('dotenv').config();
Now we need to use the webpack EnvironmentPlugin to make the environment
variables we need available. Add this to our list of plugins in
webpack.config.js
:
plugins: [
...,
new webpack.EnvironmentPlugin(["ENV_VAR", "ANOTHER_ENV_VAR"])
]
We can now reference the environment variables we configured via process.env
in our JavaScript, for example process.env.ENV_VAR
, and webpack will handle
these when compiling.
Passing Config to Elm as Flags
Elm has the concept of “flags” which the JavaScript Interop section of the guide describes as: “static configuration for your Elm program”. That sounds like exactly what we need!
So when we’re embedding our Elm app we can write the following to pass our environment variables as flags:
const elmApp = Elm.Main.embed(elmDiv, {
config_value1: process.env.ENV_VAR,
config_value2: process.env.ANOTHER_ENV_VAR
});
On the Elm side we’ll use Html.programWithFlags to receive the flags. Our main function will look something like this:
main : Program Flags Model Msg
main =
Html.programWithFlags
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
Our init
function will need to be modified to receive the flags. The argument
is of type Flags
which we’ll also need to define:
type alias Flags =
{ config_value1 : String
, config_value2 : String
}
Then, we update init
‘s type signature:
init : Flags -> ( Model, Cmd Msg )
In my app, handling the flags meant storing them in the app state to be used
later. Assuming your app state is of type Model
, init
will look something
like this, returning a Model
and a Cmd Msg
:
init : Flags -> ( Model, Cmd Msg )
init flags =
( Model flags.config_value1 flags.config_value2, Cmd.none )
That’s it! When webpack compiles our app config will be read from the environment, baked into the compiled JavaScript and available in our Elm app. It’s worth mentioning that these environment variables will be visible in the source to users, so aren’t suitable for anything sensitive.