Streamlining Go Configuration and Environment Variables

Hello everyone! This is my first attempt at writing a tech blog post in English. As a Go developer, I’ve been working on improving the way I handle configuration and environment variables in my applications, and I’d like to share my experiences and the approach I’ve settled on.
Why we use configuration in applications
Configuration and environment variables are essential for building flexible and maintainable applications. They allow us to separate configuration from code, making it easier to deploy our applications in different environments (e.g., development, staging, production) without modifying the codebase. Environment variables, in particular, are a widely accepted practice for storing sensitive information, such as database credentials and API keys, keeping them out of version control.
Common approaches
Many Go developers have been using libraries like joho/godotenv (known as "Dotenv") or spf13/viper to manage configuration and environment variables. These libraries typically read configuration from files (e.g., .env), which can be convenient during development. However, this approach can lead to inconsistencies between environments and goes against the Twelve-Factor App methodology, which states: Store config in the environment. So, I started looking for a proper way to handle configuration in Go applications.
One approach is to use Go’s built-in os.Getenv()
function to read environment variables directly. This aligns with the Twelve-Factor App methodology but can quickly become verbose, especially when dealing with default values. For example, if you can't get the value of APP_PORT
from the environment, you might want to use a default value of 8080
. With os.Getenv()
, you'd have to write something like:
port := os.Getenv("APP_PORT")
if port == ""
This approach can become cumbersome when you have multiple configuration options to handle.
A better solution
Enter caarlos0/env, a simple library that makes working with environment variables in Go a breeze. Here's an example of how you might use it:
type Config struct
func main()
In this example, we define a Config
struct with fields corresponding to our configuration options. The env and envDefault struct tags tell the env
library which environment variables to look for and what default values to use if the variables aren't set.
The env.Parse(&cfg)
call populates the cfg struct with values from the environment (or the default values if the environment variables aren't set). It's a concise and expressive way to handle configuration and environment variables in Go.
Summary
Managing configuration and environment variables is a crucial aspect of building robust and maintainable applications. While there are various approaches available, using the github.com/caarlos0/env library provides a simple and idiomatic way to handle this in Go, aligning with the Twelve-Factor App methodology. By defining a struct with appropriate tags, we can easily read configuration from the environment while providing sensible default values, resulting in cleaner and more maintainable code.