Wire the database in — main, handlers, home
Now connect the pieces. Three modifications, as tabs, following the data from its source outward: main.go → handlers.go → home.templ (start the DB, give handlers the queries, show the count). The diffs show only what changed; everything from lessons 1–2 is carried forward.
1. cmd/signflow/main.go (modify) — two steps slot in between config and serving: run the migrations, then open the pool.
if err := runMigrations(cfg.DatabaseURL, log); err != nil { // goose Up, embedded
return err
}
pool, err := db.Connect(ctx, cfg.DatabaseURL) // pgx pool
// ...
queries := db.New(pool)
h := handlers.New(cfg, queries, log) // ← now takes queries
runMigrations uses goose's database/sql handle (via the pgx stdlib driver) to apply the embedded migrations, then the app runs on a pgx pool. The static/serving/shutdown code below is unchanged.
2. internal/handlers/handlers.go (modify) — the struct gains a Queries field, New takes it, and Home reads the count:
count, err := h.Queries.CountUsers(r.Context())
// ...
render(w, r, http.StatusOK, web.Home(count))
Health now pings the DB too — an unreachable database reports 503 unhealthy instead of silently serving 500s.
3. internal/web/home.templ (modify) — Home grows a parameter and a line:
templ Home(userCount int64) { // ← was Home()
...
Registered users in the database: <strong>{ strconv.FormatInt(userCount, 10) }</strong>
Verify. Create the database, then run (remember: templ generate for the home change, sqlc generate if you haven't):
createdb signflow
templ generate
go run ./cmd/signflow
Startup now logs the migration applying:
level=INFO msg="starting signflow" env=dev port=8080 base_url=http://localhost:8080
level=INFO msg=goose msg="OK 00001_users.sql"
level=INFO msg=listening addr=:8080
Open http://localhost:8080 — the status card now reads "Registered users in the database: 0", live from Postgres. Insert one by hand and reload:
$ psql signflow -c "INSERT INTO users (email, password_hash) VALUES ('a@b.co', 'x');"
INSERT 0 1
Reload the page → "Registered users in the database: 1". The count came from the database, through your sqlc-generated CountUsers, into a type-safe template. The whole stack is wired.
Gotcha (migration already applied). Run
go runtwice and the second start logs noOK 00001_users.sql— goose records applied migrations in agoose_db_versiontable and skips them. That's the point: migrations are apply-once and safe to run on every boot. To start clean,dropdb signflow && createdb signflow.