docs(01-04): add backend/README.md quickstart (closes FOUND-05)

- Five-minute clone-to-page onboarding doc covering prereqs, bootstrap,
  two-terminal dev workflow, common commands, and troubleshooting.
- Documents docker compose fallback for contributors not on podman (D-11).
- Documents two-terminal workflow: just dev + just styles-watch (D-14).
- Every `just <recipe>` referenced is a real recipe in backend/justfile.
- Uses 'bootstrap-downloaded' wording (not 'vendored') for tailwindcss
  and htmx.min.js per Codex review concern #4.
- Clarifies HTMX runtime no-CDN policy; the unpkg URL in justfile is the
  single authoritative version pin (D-10 / Codex concern #5).
- Top 3 pitfalls from RESEARCH surfaced in Troubleshooting.
- 195 lines, no emoji, no marketing voice.
This commit is contained in:
Arthur Belleville 2026-05-14 19:31:19 +02:00
parent 709aa5cff3
commit 88f3706cd5
No known key found for this signature in database

195
backend/README.md Normal file
View file

@ -0,0 +1,195 @@
# Xtablo backend
Go + HTMX + Postgres. Phase 1: Walking Skeleton.
This README is the contract for FOUND-05: a developer with the prerequisites below
should be able to clone the repo, follow the Quickstart, and see the HTMX-driven
page within ~5 minutes.
## Prerequisites
Install these on your dev machine before starting:
- **Go** ≥ 1.22 (this project's `go.mod` declares 1.26)
- **just** — task runner (`brew install just` on macOS, `cargo install just`, or see
<https://github.com/casey/just>)
- **podman** with `podman compose` (preferred per D-11) **or** **docker** with
`docker compose`
- **curl**
- **git**
You do **not** need to install `goose`, `templ`, `sqlc`, `air`, the Tailwind CLI, or
`htmx.min.js``just bootstrap` installs the Go tools into `$GOBIN` and
bootstrap-downloads the Tailwind binary and HTMX script into local, gitignored
paths.
## Quickstart
Clone-to-running-page in ~5 minutes. Run from inside `backend/`.
```
cd backend
cp .env.example .env # adjust DATABASE_URL if Postgres is not on localhost:5432
just bootstrap # installs goose/templ/sqlc/air; bootstrap-downloads tailwindcss + htmx.min.js
just db-up # starts postgres via podman compose (see fallback below)
just migrate up # applies migrations from ./migrations
just dev # terminal 1: brings up db, runs generate, then air on :8080
# in a SECOND terminal:
just styles-watch # rebuilds static/tailwind.css on .templ / .go changes
# open http://localhost:8080
```
The page should render with a "Fetch server time" button. Clicking it swaps an
ISO-8601 timestamp into the page via HTMX. If the page shows "No time fetched
yet." and nothing happens on click, see Troubleshooting.
`bootstrap` is the slowest step (Go tool installs + two HTTP downloads). It only
needs to run once per clone.
## docker compose fallback
`compose.yaml` is portable across podman and docker — the service definition is
identical. If you don't have podman:
- Replace `podman compose` with `docker compose` mentally throughout this README.
- The `just db-up` / `just db-down` recipes call `podman compose` directly. Run
`docker compose up -d postgres` / `docker compose down` instead, and continue
with the rest of the Quickstart unchanged.
(Decision D-11.)
## Project layout
```
backend/
cmd/
web/main.go # HTTP server entry point
worker/main.go # background worker (skeleton — boot/log/shutdown only)
internal/
db/ # pgxpool wiring + sqlc-generated queries
web/ # chi router, handlers, middleware, design-system
ui/ # custom templ component library (Button, Card, Badge)
session/ # placeholder — Phase 2
tablos/ # placeholder — Phase 3
tasks/ # placeholder — Phase 4
files/ # placeholder — Phase 5
migrations/ # goose .sql migrations
templates/ # .templ files (layout, index, fragments)
static/
htmx.min.js # bootstrap-downloaded by `just bootstrap`; gitignored; no runtime CDN
tailwind.css # generated by the Tailwind standalone CLI
bin/ # gitignored — tailwindcss CLI binary, etc.
.air.toml # air live-reload config
.env.example # committed; copy to .env
compose.yaml # local Postgres
go.mod / go.sum
justfile # task runner recipes — the source of truth for commands
sqlc.yaml
tailwind.input.css
README.md
```
HTMX is served from `/static/htmx.min.js` at runtime — no CDN. The justfile's
bootstrap-time `unpkg.com` URL is the single authoritative version pin (D-10).
## Environment variables
`backend/.env` is gitignored; `backend/.env.example` is committed and lists the
three keys consumed by `cmd/web` (and `cmd/worker` for `DATABASE_URL`):
| Variable | Description | Default |
| -------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| `DATABASE_URL` | Postgres DSN used by the web + worker binaries and by `just migrate` | `postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable` |
| `PORT` | HTTP port for `cmd/web` | `8080` |
| `ENV` | `development` enables slog's text handler; `production` switches to JSON | `development` |
## Common commands
Every command in this table is a recipe in `backend/justfile`.
| Recipe | What it does | When to use |
| ----------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------- |
| `just bootstrap` | Installs Go CLI tools (`goose`, `templ`, `sqlc`, `air`); bootstrap-downloads `bin/tailwindcss` and `static/htmx.min.js` | Once per clone; re-run after deleting `bin/` or `static/htmx.min.js` |
| `just db-up` | Starts the local Postgres container | Before `just migrate up` / `just dev` if not already running |
| `just db-down` | Stops the local Postgres container | When you're done for the day |
| `just migrate up` / `migrate down` / `migrate status` | Applies / reverts / inspects goose migrations against `DATABASE_URL` | After `just db-up`, or any time you change `migrations/` |
| `just generate` | One-shot: `templ generate`, `sqlc generate`, Tailwind compile to `static/tailwind.css` | After editing `.templ`, query SQL, or `tailwind.input.css` |
| `just styles-watch` | Tailwind standalone CLI in `--watch` mode | In a second terminal alongside `just dev` (D-14) |
| `just dev` | Brings up Postgres, runs `just generate`, then runs `air` for Go live-reload on `:8080` | Main dev loop, terminal 1 |
| `just test` | `templ generate` then `go test ./...` | Before committing |
| `just lint` | `go vet ./...` and `gofmt -l` check | Before committing |
| `just build` | Generates assets, then builds `bin/web` and `bin/worker` | Producing release binaries locally |
| `just clean` | Removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files | Reset to a fresh-clone state without dropping the Postgres volume |
## Worker (skeleton — Phase 1 only)
`cmd/worker` in Phase 1 boots, logs `worker ready`, and idles waiting for a
signal. Real job runtime lands in Phase 6 (D-03). To run it manually:
```
DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable \
go run ./cmd/worker
```
Ctrl-C to exit.
## Troubleshooting
The three issues most likely to trip you up on a fresh clone:
- **"Fresh clone fails to build with `undefined: templates.Index`"** — Templ
generates `*_templ.go` files from `.templ` sources, and those generated files
are not committed. Run `just generate` (or `just dev`, which calls it) before
invoking `go build` directly. (Pitfall 1.)
- **"First request to `/healthz` returns 503 right after `just db-up`"** — The
Postgres container needs ~510 seconds to become healthy after `podman compose
up -d` returns. Check `podman compose ps` (or `docker compose ps`) for the
`healthy` status, or just wait and retry. Subsequent calls succeed. The 503
during warm-up is correct behavior, not a bug. (Pitfall 2.)
- **"Tailwind classes used in `.templ` files don't appear in the compiled CSS"** —
Tailwind v4 only scans content paths declared via `@source` in
`tailwind.input.css`. Confirm the file contains `@source
"../templates/**/*.templ";` (and equivalent globs for `internal/web/**/*.go`).
Re-run `just styles-watch` so the watcher picks up the config change.
(Pitfall 3.)
If something else is wrong and you want a clean slate without dropping the
Postgres volume:
```
just clean # removes bin/, tmp/, static/htmx.min.js, static/tailwind.css, *_templ.go
just bootstrap # re-download tools and assets
just dev # back to a working state
```
Run `just db-down` first if you also want to drop the Postgres container.
## What Phase 1 ships (and doesn't)
**Ships:**
- Project scaffold (`go.mod`, justfile, `.air.toml`, `tailwind.input.css`,
`sqlc.yaml`, `compose.yaml`)
- Local Postgres via `compose.yaml` (`pg_isready` healthcheck)
- goose migration pipeline (`migrations/0001_init.sql` is a no-op bootstrap)
- chi router with `/`, `/healthz`, `/demo/time`, `/static/*`
- slog-based structured logging with RequestID middleware
- Graceful HTTP shutdown
- pgxpool wiring exercised by `/healthz`
- templ + HTMX demo (root page + `hx-get` round-trip to a templ fragment)
- Custom templ design-system package at `internal/web/ui/` (Button, Card, Badge)
- Live-reload dev loop (`just dev` + `just styles-watch`)
- `cmd/worker` skeleton (boot, log, idle, shutdown)
**Does not ship — deferred:**
- Authentication, sessions, users → Phase 2
- Tablos CRUD → Phase 3
- Tasks / kanban → Phase 4
- File uploads + R2/S3 → Phase 5
- Real worker jobs → Phase 6
- Production deploy, Dockerfile, `/readyz` → Phase 7