How to Work with Configuration in Go: From Chaos to Order
\n\nEvery Go developer sooner or later faces the question: "How to properly store and load application settings?" The topic is so popular that on Stack Overflow, the corresponding question has gathered thousands of views and dozens of answer options. And this is no coincidence: how you organize your configuration determines how easy it will be to deploy the application, test it, and adapt it to different environments.
\n\nIn this article, we will analyze the most common questions about configuration in Go, look at real code examples, and develop an approach that will save you from headaches during deployment.
\n\nWhy is Configuration a Pain?
\n\nImagine: you wrote a great application. It works locally, tests pass, colleagues applaud. But when it comes to deploying to staging and production, the nightmare begins. Different databases, different API keys, different logging levels. And then you realize that hardcoding settings is a bad idea.
\n\nGo offers flexible tools for working with configuration, but beginners often make the same mistakes: they use global variables, confuse formats, forget about environment variables. Let's figure out how to do it right.
\n\nFrequent Question #1: JSON or YAML?
\n\nThe very first question that arises is: "What format should I store the configuration in?" The answer depends on the context, but there are general recommendations.
\n\nJSON — Simple and Native
\n\nGo natively works with JSON through the encoding/json package. This is an excellent choice for microservices and API gateways.
// config.json\n{\n "port": 8080,\n "database": {\n "host": "localhost",\n "port": 5432,\n "name": "myapp"\n },\n "log_level": "info"\n}\n\n// main.go\npackage main\n\nimport (\n "encoding/json"\n "fmt"\n "os"\n)\n\ntype Config struct {\n Port int `json:"port"`\n Database Database `json:"database"`\n LogLevel string `json:"log_level"`\n}\n\ntype Database struct {\n Host string `json:"host"`\n Port int `json:"port"`\n Name string `json:"name"`\n}\n\nfunc main() {\n file, _ := os.Open("config.json")\n defer file.Close()\n \n var config Config\n json.NewDecoder(file).Decode(&config)\n \n fmt.Printf("Starting server on port %d\\n", config.Port)\n fmt.Printf("Connecting to DB %s:%d/%s\\n", \n config.Database.Host, \n config.Database.Port, \n config.Database.Name)\n}\n\nYAML — Readability Above All
\n\nFor complex configurations with many nested fields, YAML is more convenient. However, it requires a third-party library — for example, gopkg.in/yaml.v3.
// config.yaml\nserver:\n port: 8080\n timeout: 30s\n\ndatabase:\n host: localhost\n port: 5432\n name: myapp\n\nlogging:\n level: info\n format: json\n\npackage main\n\nimport (\n "fmt"\n "gopkg.in/yaml.v3"\n "os"\n)\n\ntype Config struct {\n Server ServerConfig `yaml:"server"`\n Database DatabaseConfig `yaml:"database"`\n Logging LoggingConfig `yaml:"logging"`\n}\n\nfunc main() {\n data, _ := os.ReadFile("config.yaml")\n var config Config\n yaml.Unmarshal(data, &config)\n \n fmt.Printf("Server timeout: %s\\n", config.Server.Timeout)\n}\n\nTip: Use YAML for configurations that people will read, and JSON for machine-to-machine data exchange.
\n\nFrequent Question #2: What About Environment Variables?
\n\nIn modern development (especially in containers and Kubernetes), environment variables have become the de facto standard. They allow you to easily change settings without rebuilding the application.
\n\nGo supports working with env through os.Getenv() and os.LookupEnv(). But manually parsing each variable is tedious and