Theme
/
Theory

One source of truth — Postgres, goose, sqlc

The home page has nothing real to say — its status card is hardcoded. Time to connect a database. By the end of this lesson Home will show a live count of registered users, read from Postgres.

Three tools, three jobs:

  • PostgreSQL — the database.
  • goosemigrations. Each schema change is a numbered .sql file (00001_users.sql). goose applies pending ones in order and remembers which have run. We embed them and run them on startup, so a fresh deploy migrates itself.
  • sqlcqueries. You write plain SQL; sqlc generates type-safe Go functions from it. No ORM, no query-builder DSL — just SQL, with the compiler checking the Go side.

The lovely part — one source of truth. Here is the fact worth stopping on:

sqlc reads its schema straight from the goose migration files. Look at sqlc.yaml: schema: "db/migrations". sqlc doesn't have its own copy of the table definitions — it reads the same .sql files goose applies to the real database. So the generated Go cannot drift from the actual schema.

If you've used a typical ORM, you know the usual arrangement: the migrations describe the database, and separately a set of model classes describe what the code thinks the database looks like — and those two quietly diverge. You add a column in a migration, forget to update the model, and nothing complains until runtime. That whole category of bug cannot exist here, because there is only one description of the schema. Change the migration → regenerate → the Go types change to match, at compile time. Rename a column and forget to update a query? sqlc generate fails. The drift is caught before the code even builds.

You'll prove this in the homework: add a column to the migration, regenerate, and watch the generated Go grow the field with zero manual edits.

This lesson builds the data layer:

  1. A users table (goose migration) + embedding the migrations.
  2. sqlc.yaml + a query, generated into Go.
  3. A pgx connection pool.
  4. Wire it into main and Home — the live count.

Prerequisite — a running Postgres. You need Postgres locally. createdb signflow and you're set; the config default connects to postgres://postgres@localhost:5432/signflow.

No admin rights / Windows? scoop install postgresql installs a user-level Postgres (no admin needed), then start it with pg_ctl -D "$env:USERPROFILE\scoop\apps\postgresql\current\data" start.