Files
llm-browser/backend/README.md
T
2026-10-19 16:04:17 +02:00

115 lines
4.6 KiB
Markdown

# fullstack-template backend
Go 1.27 API server template: stdlib `net/http` routing (Go 1.22+ method
patterns with `r.PathValue`), [Bun](https://bun.uptrace.dev) ORM (SQLite
file by default, Postgres via `DATABASE_URL`), and a layered layout
(handler → service → repository behind interfaces) designed to be copied
per resource and iterated on with an LLM.
## Quickstart
No containers, no database server — the default database is a local SQLite
file (`dev.db`):
```sh
cd backend
make migrate-up # apply migrations (creates dev.db)
make run # API on :8080
```
Try it:
```sh
curl -s localhost:8080/api/v1/items
curl -s -X POST localhost:8080/api/v1/items \
-H 'Content-Type: application/json' \
-d '{"title":"First item","description":"Hello template"}'
curl -s localhost:8080/api/v1/items/1
curl -s -X PATCH localhost:8080/api/v1/items/1 \
-H 'Content-Type: application/json' -d '{"status":"archived"}'
curl -s -i -X DELETE localhost:8080/api/v1/items/1
```
## Endpoints
| Method | Path | Notes |
|---|---|---|
| GET | `/healthz` | liveness |
| GET | `/readyz` | checks the database |
| GET | `/api/auth/session` | who is signed in; always 200, `{"authenticated":bool,"user":…\|null}` |
| GET | `/api/auth/login` | starts the OIDC redirect; optional `?returnTo=/path` |
| GET | `/api/auth/callback` | issuer lands here; do not call directly |
| POST | `/api/auth/logout` | clears the session cookie; 204 |
| GET | `/api/v1/items` | `?status=active|archived&limit=&offset=` — requires a session when auth is on |
| POST | `/api/v1/items` | 201 + `Location`, body `{"title","description","status"}` |
| GET | `/api/v1/items/{id}` | |
| PATCH | `/api/v1/items/{id}` | partial update; `null`-absent fields untouched |
| DELETE | `/api/v1/items/{id}` | 204 |
Errors are always `{"error":{"code":"...","message":"..."}}`.
## OIDC authentication
Set `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` (see [.env.example](.env.example))
and the API authenticates against the issuer at `OIDC_ISSUER`
(default `https://users.k6n.net`) using the authorization-code + PKCE flow:
- The browser is sent to the issuer from `GET /api/auth/login`; the callback
path is fixed at `/api/auth/callback`. The redirect URI is derived from the
incoming request (`X-Forwarded-Host`/`X-Forwarded-Proto` when behind the
Vite proxy, `Host`/TLS otherwise), so the same server works on
`localhost:<any port>` (loopback exception), `*.preview.k6n.net`
(registered wildcard), and the API origin itself — as long as those URIs
are registered for the client with the issuer.
- The signed-in identity (sub, name, email, roles) is kept in an
HMAC-SHA256-signed session cookie (`HttpOnly`, `SameSite=Lax`). ID and
access tokens never reach the browser and are not persisted server-side;
- When auth is enabled, everything under `/api/v1/` answers
`401 unauthorized` without a valid session. `/healthz`, `/readyz`, and
`/api/auth/*` stay public.
- Without `OIDC_CLIENT_ID` the server runs standalone: no `/api/auth`
routes and an open `/api/v1` (logged loudly at boot).
`AUTH_SESSION_SECRET` signs the cookies — unset it only in development (a
random one is generated per boot, invalidating all sessions on restart).
`AUTH_SESSION_TTL` (default `168h`) bounds the session.
## Layout
```
cmd/
api/ server entrypoint (config, wiring, graceful shutdown)
migrate/ migration CLI: up | down | status | new
internal/
api/ routing, middleware, JSON conventions, item + auth handlers
auth/ OIDC client (code + PKCE flow) and signed session cookies
config/ env parsing — the only place reading os.Getenv
database/ bun connection (sqlite or postgres) + migrator
migrations/ one Go file per migration: <timestamp>_<name>.go
items/ reference resource: model, Repository interface,
Bun implementation, service (business rules)
compose.yaml optional dev Postgres
Makefile `make check` = fmt-check + vet + test
```
`internal/items` is the pattern to copy when adding a resource — see
AGENTS.md for the step-by-step recipe.
## Configuration
All settings come from the environment and a `.env` file in backend/ is
autoloaded. The database defaults to the SQLite file `dev.db`; for
Postgres run `make db-up` and set
`DATABASE_URL=postgres://fullstack:fullstack@localhost:5432/fullstack?sslmode=disable`.
See [.env.example](.env.example) for the full list.
## Tests
```sh
make test # or: go test ./...
```
Service and handler tests run against in-memory fakes; the database test
runs the real migrations and repository against in-memory SQLite — no
database server needed for any of them.