minienv
is a minimal libary that makes it easy to work with environment variables in Go. It is heavily inspired by netflix/go-env
and Pythons pydantic/BaseSettings
and combines reading from .env
files and reflection based parsing of environment variables.
Add it with the following command:
go get github.com/yannickalex07/minienv
Using minienv
is quite simple, just create a struct and annotate it with env:""
tags:
type Environment struct {
Port int `env:"PORT"`
}
var e Environment
if err := minienv.Load(&e); err != nil {
// handle error
}
print(e.Port) // will equal to whatever the PORT env variable is set to
By default every value is required, so if no matching env variables was found or no default is specified, the load will fail with an error. This can be changed by declaring a certain field as optional in the tag:
type Environment struct {
Port int `env:"PORT,optional"`
}
var e Environment
if err := minienv.Load(&e); err != nil {
// handle error
}
print(e.Port) // will be the default value of PORT was not set
Minienv allows you to specify default values that will be used if no value was found in the environment or specified through a fallback like WithFile()
or WithFallbackValues()
.
type Environment struct {
Port int `env:"PORT,default=8080"`
}
var e Environment
// `WithFile()` with no arguments will look for a `.env` file in the current directory
err := minienv.Load(&e)
if err != nil {
// handle error
}
print(e.Port) // will be 8080 if PORT is not set in the environment
minienv
additionally supports loading variables from .env
files by using the WithFile(...)
option:
type Environment struct {
Port int `env:"PORT"`
}
var e Environment
// `WithFile()` with no arguments will look for a `.env` file in the current directory
err := minienv.Load(&e, minienv.WithFile(false))
if err != nil {
// handle error
}
Alternatively you can specify one or multiple explicit files:
type Environment struct {
Port int `env:"PORT"`
}
var e Environment
err := minienv.Load(&e, minienv.WithFile(true, "database.env", "extra.env"))
if err != nil {
// handle error
}
The first argument controls if the files are required to be there or not. false
indicates that the load will just continue if the file / files were not found, a true
on the other hand would raise an error if a file was not found of couldn't be parsed.
Precedence Order: Values from .env
-files have a lower precedence than environment variables, therefore if a key exists in the environment and in a .env
-file, there value in the environment takes precedence. Also, if a key exists in multiple .env
-files, the last value takes precedence.
The following features are more advanced, however some of them might still be useful.
Another option that minienv
provides is to supply custom fallback values that might be sourced from somewhere completely else:
type Environment struct {
Port int `env:"PORT"`
}
values := map[string]string{
"PORT": "12345"
}
var e Environment
err := minienv.Load(&e, minienv.WithFallbackValues(values))
if err != nil {
// handle error
}
Another option allows you to set a prefix that will be used during environment lookup:
type Environment struct {
Port int `env:"PORT"`
}
var e Environment
err := minienv.Load(&e, minienv.WithPrefix("APP_")) // will cause a lookup for APP_PORT
if err != nil {
// handle error
}
This prefix is also applied to keys from .env
-files as well as additional fallback values, however only if the key does not already contain the prefix.
If Minienv encounters any issues during loading, it will raise an error to the enduser. These errors are wrapped in custom error objects that allow you to react to them more precisely.
If the input to the Load()
-function itself is invalid, Minienv will raise the predefined ErrInvalidInput
-error:
var e Environment
err := minienv.Load(e) // e is not a pointer here, therefore invalid
if err == minienv.ErrInvalidInput {
// do something...
}
Additionally if Minienv fails to load a value into a certain field, for example due to a type mismatch, it will raise an error of type LoadError
:
var e Environment
err := minienv.Load(&e)
if err != nil {
loadErr = err.(minienv.LoadError)
// handle load error
}
The LoadError
additionally exposes the affected field that failed together with the underlying error.