Hi there! I'm Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.
Managing config in Go projects is simple—until you have to decide how to do it. Two popular options are .env
files and .toml
files. Let’s break down what each does, when to use them, and how to implement both cleanly.
.env
— The Minimalist's Choice
.env
files are basically key-value pairs like:
PORT=8080
DB_URL=postgres://user:pass@localhost:5432/dbname
DEBUG=true
How to use in Go
Use the github.com/joho/godotenv
package:
go get github.com/joho/godotenv
package main
import (
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
port := os.Getenv("PORT")
log.Println("Server will run on port:", port)
}
Pros
- Dead simple
- Popular in 12-factor apps
- Great for secrets + env-specific settings
Cons
- No support for structured config (arrays, nested data)
- All values are strings
.toml
— Structured and Powerful
.toml
stands for "Tom's Obvious, Minimal Language". It supports rich types: strings, ints, arrays, nested tables.
[server]
port = 8080
debug = true
[database]
url = "postgres://user:pass@localhost:5432/dbname"
max_connections = 20
🛠 How to use in Go
Use github.com/pelletier/go-toml
:
go get github.com/pelletier/go-toml
package main
import (
"fmt"
"log"
"github.com/pelletier/go-toml"
)
type Config struct {
Server struct {
Port int
Debug bool
}
Database struct {
URL string
MaxConnections int `toml:"max_connections"`
}
}
func main() {
var config Config
if _, err := toml.DecodeFile("config.toml", &config); err != nil {
log.Fatal(err)
}
fmt.Println("DB URL:", config.Database.URL)
}
Pros
- Native support for structured configs
- Type-safe
- Better for large configs
Cons
- More verbose
- Requires struct mapping
When to Use What?
Use Case | Use .env
|
Use .toml
|
---|---|---|
Small config or secrets | ✅ | 🚫 |
Structured config (nested) | 🚫 | ✅ |
Environment-specific overrides | ✅ | ✅ (with profiles) |
Type safety | 🚫 | ✅ |
Simplicity | ✅ | 🚫 |
Combine Both?
Yes, many real-world apps load secrets from .env
and structured settings from .toml
. Example:
# .env
DB_PASSWORD=supersecret
# config.toml
[database]
user = "admin"
host = "localhost"
// Load .env
_ = godotenv.Load()
password := os.Getenv("DB_PASSWORD")
// Load config.toml
var config Config
_, _ = toml.DecodeFile("config.toml", &config)
// Combine them
dsn := fmt.Sprintf("postgres://%s:%s@%s", config.Database.User, password, config.Database.Host)
Final Thoughts
- Use
.env
when you want simplicity or are working with containerized/12-factor apps. - Use
.toml
when your config has structure, or you want clean type mapping. - Use both when it makes sense—there’s no rule saying you can’t.
LiveAPI helps you get all your backend APIs documented in a few minutes.
With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.
If you're tired of updating Swagger manually or syncing Postman collections, give it a shot.
Top comments (2)
I tend to use only environment variables and inject them using dotenvx if there is need for a config file.
That's amazing..🤌❤️🔥.env for secrets, .toml for organized config. Most devs use both - .env for stuff that changes, .toml for stuff that stays the same. Simple.