When to use runtime.exs for app configuration?

I’m building an API server with various HTTP endpoints.

I load user config like API keys and such from config.yml, mounted to a docker instance. I implemented AppConfig to abstract this. Various modules in my application (a Phoenix Framework application) code use AppConfig. I’m wondering if it’s better practice to incorporate runtime.exs somehow? I didn’t grow up with Elixir, so any advice would be appreciated.

defmodule MyApp.AppConfig do
  defp read do
    {:ok, yml} = YamlElixir.read_from_file("config.yml")
    yml
  end

  @doc """
  get(["app", "api_key"], "app_api_key", "default value")
  """
  def get(path, env_key, default \\ nil) do
    case read() |> get_in(path) do
      nil ->
        case System.get_env(env_key) do
          nil -> default
          value -> value
        end
      value -> value
    end
  end

  def get!(path, env_key, default \\ nil) do
    case get(path, env_key, default) do
      nil -> raise "Path not found: \"#{path}\". Env \"#{env_key}\" also not found"
      value -> value
    end
  end
end
1 Like

You would be better off just using Vapor IMO. It is started together with your app so when your app finishes loading you can use anything loaded from the config. It can load env vars and YAML / JSON files (and others).

It has a quick example in the docs but if you need something that was actually in a project running in production and you can’t formulate it yourself, I can help you further by pasting snippets.

1 Like

Brilliant, thanks for making me aware of Vapor. :slight_smile:

1 Like

Using runtime.exs is just fine. Vapor was created before runtime.exs was added to Elixir.

Your code reads a file every time you call get function. This is not optimal. Just read it once in runtime.exs and set configs there.
I prefer to avoid calling System.get_env in runtime (in runtime, not in runtime.exs!!). Types of values in both yaml and ENV are quite primitive. There are no atoms, tuples there, etc.
Using Application.get_env is much better because of this. Also, if you put all configs in the corresponding folder, it will be much easier to understand how the app can be configured at all.

Sure, use Vapor if you like it, but having runtime.exs is totally fine.

2 Likes

I mostly settled on Vapor because it can do some data validation + it does not care very much where does the config come from.

I dislike huge libraries and too many abstractions but as IMO you yourself discovered (as you have written a config library yourself) it does get annoying to read stuff from env vars or files etc. and try to do validation. Having a library + a basic DSL to do it for you is an okay compromise though I’d definitely look for something more lightweight than Vapor in the future (it’s too big for my taste).

The “official” way to do what you want is using a Config.Provider.

2 Likes

I started writing a small env parser that uses Want - Type Conversion and Coercion Library which I still want to improve by making a couple PRs to Want and then need to properly test in prod before releasing: env_config.exs · GitHub