Enviable

Hex.pm Hex Docs Apache 2.0 Coverage

Enviable is a small collection of functions to improve Elixir project configuration via environment variables as proposed under the 12-factor application model. It works well with configuration environment loaders like Dotenvy, Nvir, or Envious and provides robust value conversion like jetenv.

Enviable 3 adds an alternative interface, Enviable.Codex. It's not interoperable with Enviable, and only one should be used, not both in the same module. The main new features in Codex are:

All Enviable.Codex value retrieval functions all return a struct implementing String.Chars; it can be used in string interpolation just like values from System.get_env/1,2. Outside of string interpolation, as_string/1 is recommended.

There are breaking changes described in the CHANGELOG.

Enviable 3.1 extends functionality to simplify three patterns seen in Phoenix runtime configuration templates.

Elixir 1.17 or later is required.

Usage

Enviable or Enviable.Codex will typically be imported in config/runtime.exs after Config, but may be used anywhere that environment variables are read.

Enviable.Codex with Nvir

The Nvir dotenv* functions always return an environment map, but it automatically pushes all values back into the process environment table.

import Nvir
import Enviable.Codex

client = fetch_env!("CLIENT")
dotenv!([".env", ".env.#{client}"])

config :my_app,
  key: fetch_env_once!("SECRET_KEY") |> as_string(),
  port: fetch_env!("PORT") |> as_integer(),
  ssl: get_env("SSL_ENABLED") |> as_boolean()

Enviable with Nvir

import Nvir
import Enviable

client = fetch_env!("CLIENT")
dotenv!([".env", ".env.#{client}"])

config :my_app,
  key: fetch_env!("SECRET_KEY"),
  port: fetch_env_as_integer!("PORT"),
  ssl: get_env_as_boolean("SSL_ENABLED")

Enviable.Codex with Dotenvy

The use of a side_effect calling System.put_env/1 is discouraged with Enviable.Codex.

import Config
import Enviable.Codex

client = fetch_env!("CLIENT")
values = Dotenvy.source!([".env", ".env.#{client}", get_env()])

config :my_app,
  key: fetch_env_map!(values, "SECRET_KEY") |> as_string(),
  port: fetch_env_map!(values, "PORT") |> as_integer(),
  ssl: get_env_map(values, "SSL_ENABLED") |> as_boolean()

Enviable with Dotenvy

The use of a side_effect that calls System.put_env/1 is required with Enviable.

# config/runtime.exs
import Config
import Enviable

client = fetch_env!("CLIENT")
Dotenvy.source([".env", ".env.#{client}", get_env()], side_effect: &put_env/1)

config :my_app,
  key: fetch_env!("SECRET_KEY"),
  port: fetch_env_as_integer!("PORT"),
  ssl: get_env_as_boolean("SSL_ENABLED")

Enviable.Codex with Envious

Envious parses a single file at a time and doesn't write to the environment variable table.

import Config
import Enviable.Codex

client = fetch_env!("CLIENT")
env_files = [".env", ".env.#{client}"]

loaded_env =
  Enum.reduce(env_files, %{}, fn file, acc ->
    case File.read(file) do
      {:ok, contents} -> Map.merge(acc, Envious.parse!(contents))
      _ -> acc
    end
  end)

values = Map.merge(loaded_env, get_env())

config :my_app,
  key: fetch_env_map!(values, "SECRET_KEY") |> as_string(),
  port: fetch_env_map!(values, "PORT") |> as_integer(),
  ssl: get_env_map(values, "SSL_ENABLED") |> as_boolean()

Enviable with Envious

Envious parses a single file at a time and doesn't write to the environment variable table. To work with Enviable, we put the values into the environment with put_env_new/2.

import Config
import Enviable

client = fetch_env!("CLIENT")

env_files = [".env", ".env.#{client}"]

loaded_env =
  Enum.reduce(env_files, %{}, fn file, acc ->
    case File.read(file) do
      {:ok, contents} -> Map.merge(acc, Envious.parse!(contents))
      _ -> acc
    end
  end)

for {key, value} <- loaded_env, do: put_env_new(key, value)

config :my_app,
  key: fetch_env!("SECRET_KEY"),
  port: fetch_env_as_integer!("PORT"),
  ssl: get_env_as_boolean("SSL_ENABLED")

Installation

Enviable can be installed by adding enviable to your list of dependencies in mix.exs:

def deps do
  [
    {:enviable, "~> 3.0"},
    # Optional, required for decimal conversion
    {:decimal, "~> 2.3"},
    # Optional, required only if :json or JSON aren't available or another
    # JSON engine is not configured
    {:jason, "~> 1.4"},
    # Optional, if using the Enviable credo checks
    {:credo, "~> 1.7", only: [:dev, :test], runtime: false}
  ]
end

Documentation is found on HexDocs.

Semantic Versioning

Enviable follows Semantic Versioning 2.0.