Coding Conventions
How Informd backend code is written — namespaces, forms lifecycle, public routes.
Spec-first development
api-spec.ymlis the contract and the single source of truth.- Regenerate bindings after touching it:
just generate-oapi informd(internal/openapi/*.gen.go, not committed). - The embedded spec is served at
/docs/openapi.yml— this site's API reference is that file.
Feature layout
One directory per feature under internal/handlers/ (namespaces, forms, steps, fields, responses). Routes are registered in internal/app/router.go.
Two route shapes
Most operations exist twice: on /forms/... (acting as the calling user) and on /namespaces/{namespace_id}/forms/... (scoped, acting on a namespace's form). Keep both in sync when adding behavior:
- Namespace routes require a JWT (
jwtonly) — no API-key calls on/namespaces.... /forms/...(non-namespaced) accepts either JWT or API key (anyAuth).
Public routes (no auth)
Two routes are deliberately public — the answer view and the submission endpoint:
GET /forms/{form_id}/asnwerable— public answer view. The spelling is part of the interface ("asnwerable"); don't "fix" it in clients or specs.POST /forms/{form_id}/responses— public submission.
Public routes must not leak admin data (field definitions, member lists, internal ids that reveal structure beyond what the answer view needs).
Form lifecycle
Forms move through drafted → open → closed → archived (and redrafted). The lifecycle ops are explicit: open, close, archive, redraft. Enforce transitions server-side — a closed form must not accept submissions (public POST included).
Response envelope & errors
- Every response is the
fun.Responseenvelope; failures areAppErrorwith sharederrxsentinels. - Owner/member-only operations return
404for outsiders — no existence leak. GET /forms/{id}/responses/countis a count endpoint; don't return full response payloads there.
Database
- Schema in
db/, sqlc queries ininternal/sqlc, repos ininternal/repos.
Tests & quality
- Parity tests in
internal/appkeep the router surface in sync with the spec. just informd test(gotestsum),just informd lint(golangci-lint).
Don't
- Don't reimplement HTTP serving/telemetry — boot through
lib/go/httpserver. - Don't rename the
asnwerableroute. - Don't return the envelope root as the payload.