Code

The module and config

First, the module. go.mod isn't a file you hand-write — the Go tools manage it. go mod init creates it, and go get adds dependencies. Run:

go mod init github.com/programuoki/signflow
go get github.com/go-chi/chi/v5          # our one dependency this lesson: the router

go.mod grows as the app does — Templ joins in lesson 2, Postgres and goose in lesson 3 — but you'll grow it with go get, not by editing it, so it stays out of the code panel. (go mod tidy keeps the indirect dependencies honest.)

Now the real file — internal/config/config.go (in the code column) — configuration from the environment. The rule for the whole app: defaults that let you go run with zero setup in dev, but every real value comes from the environment in prod.

func Load() (Config, error) {
	cfg := Config{
		Port:    getenv("PORT", "8080"),   // Railway sets $PORT
		Env:     getenv("APP_ENV", "dev"),
		BaseURL: os.Getenv("BASE_URL"),
	}
	if cfg.BaseURL == "" {
		cfg.BaseURL = "http://localhost:" + cfg.Port
	}
	return cfg, nil
}

Note Config starts small — three fields — and the IsProd() helper reads Env. Later lessons add fields (a database URL next lesson, a session secret in lesson 4) rather than rewriting the file. Load already returns an error even though nothing fails yet: production will add required-value checks, and keeping the signature stable now means later lessons only add to this function.

Why internal/? A Go package under internal/ can only be imported by code rooted in the same module. It's a compiler-enforced "private": no one can import github.com/programuoki/signflow/internal/config from outside SignFlow. Everything but cmd/ lives under internal/.