Configuring an Elm App with Environment Variables via webpack

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.