elixir-toniq/vapor
{ "createdAt": "2018-05-27T17:46:13Z", "defaultBranch": "main", "description": "Runtime configuration system for Elixir", "fullName": "elixir-toniq/vapor", "homepage": "", "language": "Elixir", "name": "vapor", "pushedAt": "2023-04-10T11:02:31Z", "stargazersCount": 595, "topics": [ "configuration", "elixir" ], "updatedAt": "2025-11-11T14:56:46Z", "url": "https://github.com/elixir-toniq/vapor"}Loads dynamic configuration at runtime.
Sponsors
Section titled “Sponsors”This project is sponsored by these fine folks:
If you’re interested in sponsoring you can do so here: https://github.com/sponsors/keathley
Why Vapor?
Section titled “Why Vapor?”Dynamically configuring elixir apps can be hard. There are major differences between configuring applications with mix and configuring applications in a release. Vapor wants to make all of that easy by providing an alternative to mix config for runtime configs. Specifically Vapor can:
- Find and load configuration from files (JSON, YAML, TOML).
- Read configuration from environment variables.
.envfile support for easy local development.
Example
Section titled “Example”defmodule VaporExample.Application do use Application alias Vapor.Provider.{File, Env}
def start(_type, _args) do providers = [ %Env{bindings: [db_url: "DB_URL", db_name: "DB_NAME", port: "PORT"]}, %File{path: "config.toml", bindings: [kafka_brokers: "kafka.brokers"]}, ]
# If values could not be found we raise an exception and halt the boot # process config = Vapor.load!(providers)
children = [ {VaporExampleWeb.Endpoint, port: config.port}, {VaporExample.Repo, [db_url: config.db_url, db_name: config.db_name]}, {VaporExample.Kafka, brokers: config.kafka_brokers}, ]
opts = [strategy: :one_for_one, name: VaporExample.Supervisor] Supervisor.start_link(children, opts) endendPrecedence
Section titled “Precedence”Vapor merges the configuration based on the order that the providers are specified.
providers = [ %Dotenv{}, %File{path: "$HOME/.vapor/config.json", bindings: []}, %Env{bindings: []},]Env will have the highest precedence, followed by File, and finally Dotenv.
Reading config files
Section titled “Reading config files”Config files can be read from a number of different file types including JSON, TOML, and YAML. Vapor determines which file format to use based on the file extension.
Options on bindings
Section titled “Options on bindings”Bindings for %Env{} and %File{} providers support a number of options:
:map- Allows you to pass a “translation” function with the binding.:default- If the value is not found then the default value will be returned instead. Defaults always skip the translations.:required- Marks the binding a required or not required (defaults to true). If required values are missing, and there is no default present, then the provider will return an exception. If the binding is markedrequired: false, then the provider returns the key with anilvalue.
providers = [ %Env{ bindings: [ {:db_name, "DB_NAME"}, {:db_port, "DB_PORT", default: 4369, map: &String.to_integer/1}, ] }]Adding configuration plans to modules
Section titled “Adding configuration plans to modules”Vapor provides a Vapor.Plan behaviour. This allows modules to describe a provider
or set of providers.
defmodule VaporExample.Kafka do @behaviour Vapor.Plan
@impl Vapor.Plan def config_plan do %Vapor.Provider.Env{ bindings: [ {:brokers, "KAFKA_BROKERS"}, {:group_id, "KAFKA_CONSUMER_GROUP_ID"}, ] } endend
config = Vapor.load!(VaporExample.Kafka)Planner DSL
Section titled “Planner DSL”While using the structs directly is a perfectly reasonable option, it can often be verbose. Vapor provides a DSL for specifying configuration plans using less lines of code.
defmodule VaporExample.Config do use Vapor.Planner
dotenv()
config :db, env([ {:url, "DB_URL"}, {:name, "DB_NAME"}, {:pool_size, "DB_POOL_SIZE", default: 10, map: &String.to_integer/1}, ])
config :web, env([ {:port, "PORT", map: &String.to_integer/1}, ])
config :kafka, VaporExample.Kafkaend
defmodule VaporExample.Application do use Application
def start(_type, _args) do config = Vapor.load!(VaporExample.Config)
children = [ {VaporExampleWeb.Endpoint, config.web}, {VaporExample.Repo, config.db}, {VaporExample.Kafka, config.kafka}, ]
opts = [strategy: :one_for_one, name: VaporExample.Supervisor] Supervisor.start_link(children, opts) endendCustom Providers
Section titled “Custom Providers”There are several built in providers
- Environment
- .env files
- JSON
- YAML
- TOML
If you need to create a new provider you can do so with the included
Vapor.Provider protocol.
defmodule MyApp.DatabaseProvider do defstruct [id: nil]
defimpl Vapor.Provider do def load(db_provider) do end endendWhy does this exist?
Section titled “Why does this exist?”While its possible to use Elixir’s release configuration for some use cases, release configuration has some issues:
- If configuration ends up in Application config then its still functioning as a global and is shared across all of your running applications.
- Limited ability to recover from failures while fetching config from external providers.
- Its difficult to layer configuration from different sources.
Vapor is designed to solve these problems.
Installing
Section titled “Installing”Add vapor to your mix dependencies:
def deps do [ {:vapor, "~> 0.10"}, ]endResources from the community
Section titled “Resources from the community”Configuring your Elixir Application at Runtime with Vapor
Copyright and License
Section titled “Copyright and License”Copyright (c) 2020 Christopher Keathley
This library is released under the MIT License. See the [LICENSE.md]!(./LICENSE.md) file.