minienv is a dependency-free package 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 reflection based parsing of environment variables with reading from .env files.
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 {
Value int `env:"VALUE"`
}
var e Environment
if err := minienv.Load(&e); err != nil {
// handle error
}
print(e.Value) // will equal to whatever the VALUE env variable is set toThe package supports specifying values as optional or providing a default for them. This can be done by using the optional and default=... keywords in the env:""-tag:
type Environment struct {
Value int `env:"VALUE,optional"` // will be set to the zero value if not provided
Other string `env:"OTHER,default=test"` // will be set to "test" if not provided
}To see the correct syntax for setting default values for map and slice types in the list of supported types.
minienv supports loading variables from .env files by using the WithEnvFile(...) option:
type Environment struct {
Port int `env:"PORT"`
}
var e Environment
// the first argument is the path to the env-file and the
// second controls if it is required or not.
err := minienv.Load(&e, minienv.WithEnvFile(".env", false))
if err != nil {
// handle error
}It is possible to read from multiple .env-files by simply providing the option multiple times.
The following table provides an overview of all supported types and the syntax for default values:
| Type | Default Example |
|---|---|
int, int8, int16, int32 & int64 |
default=1 |
float32 & float64 |
default=1.1 |
bool |
default=true |
string |
default=example |
[]... (Slices support types listed above) |
default=val|val|val |
map[...]... (Maps support types listed above) |
default=key:val|key:val |
Another option allows you to set a prefix that will be used during environment lookup. This is applied to all environment variables:
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.
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
}
print(e.Port) // 12345If 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 (e.g. not a pointer), 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...
}If Minienv fails to load one or more fields, for example due to a type mismatch or a missing value, it collects all errors and returns them as a LoadErrors slice (note: Prior to v1.3, the first encountered FieldError was returned immediately). You can use errors.As to inspect the errors:
var e Environment
err := minienv.Load(&e)
if err != nil {
var loadErrors minienv.LoadErrors
if errors.As(err, &loadErrors) {
// check each field error on its own
for _, e := range loadErrors {
var fieldErr minienv.FieldError
if errors.As(e, &fieldErr) {
fmt.Printf("field %s: %s\n", fieldErr.Field, fieldErr.Err)
}
}
}
}Each FieldError exposes the affected field name and the underlying error.