Appearance
The ten-minute tour
The pitch says you can understand this codebase in ten minutes. Here is that claim, made falsifiable: one request, followed from the browser to the database and back. Everything else in the kit is a variation on what you are about to read.
The request is POST /api/orgs/{orgID}/invitations — an admin inviting a teammate. It is the most representative one we have: it crosses sessions, CSRF, tenancy, RBAC, validation, a domain service, generated queries and email, and every one of those crossings is the same in every other route.
1. The route (internal/http/server.go)
Routes are assembled in one file, and the tree is the documentation:
go
api.Route("/orgs", func(or chi.Router) {
or.Use(authMW.RequireAuth)
// ...
or.Route("/{orgID}", func(one chi.Router) {
one.Use(orgMW.Context)
// ...
one.Group(func(adm chi.Router) {
adm.Use(middleware.RequireOrgRole(org.RoleAdmin))
adm.Get("/invitations", orgsH.Invitations)
adm.Post("/invitations", orgsH.Invite)
adm.Delete("/invitations/{invID}", orgsH.RevokeInvitation)
})
})
})Read it bottom-up and you have the security model: to reach Invite you must hold a session (RequireAuth), belong to this organization (orgMW.Context) and be at least an admin in it (RequireOrgRole). There is no annotation system and no route registry — chi middleware composition, nothing else.
Before any of that ran, two global layers already did their work: the session (server-side, alexedwards/scs, loaded from the gvk_session cookie) and CSRF (gorilla/csrf — every mutating request carries a token the SPA fetched from /api/auth/csrf).
2. Tenancy (internal/http/middleware/org.go)
orgMW.Context is the row-level multi-tenancy anchor. It resolves the {orgID} route param against the caller's memberships, not against the organizations table:
go
m, err := o.Q.GetOrgMember(r.Context(), sqlcgen.GetOrgMemberParams{
OrganizationID: orgID, UserID: u.ID,
})
if errors.Is(err, sql.ErrNoRows) {
writeError(w, http.StatusNotFound, "not found")
return
}A non-member gets a 404 — indistinguishable from an organization that does not exist, so tenant IDs never leak. What lands in the request context is the organization and the caller's role in it; every handler downstream scopes its queries with oc.Org.ID and never trusts a client-supplied organization ID for anything.
3. The handler (internal/http/handlers/orgs.go)
Handlers are deliberately boring: decode, validate, delegate, map errors to statuses.
go
func (h *Orgs) Invite(w http.ResponseWriter, r *http.Request) {
oc, _ := middleware.OrgFrom(r.Context())
u, _ := middleware.UserFrom(r.Context())
var req struct {
Email string `json:"email" validate:"required,email,max=254"`
Role string `json:"role" validate:"required,oneof=admin member"`
}
if !h.decodeValid(w, r, &req) {
return
}
if req.Role == org.RoleAdmin && oc.Role != org.RoleOwner {
Error(w, http.StatusForbidden, "only owners can invite admins")
return
}
err := h.Svc.Invite(r.Context(), oc.Org, u.ID, u.Locale, req.Email, req.Role)
// ... errors.Is → 409 / 403 / 500
}Two things worth noticing. Validation is declarative (go-playground/validator) and oneof=admin member means the owner role cannot be requested at all — granting ownership is a separate, deliberate action. And the rule "inviting an admin takes an owner" lives here, in plain if, where you can read it — not in a policy engine.
4. The domain service (internal/org/org.go)
Business rules live in a plain struct with a sqlc handle, a mailer and a logger — no interfaces until a second implementation exists:
go
func (s *Service) Invite(ctx context.Context, o sqlcgen.Organization, inviterID, inviterLocale, emailAddr, role string) error {
emailAddr = auth.NormalizeEmail(emailAddr)
// Already a member? Already invited? → ErrAlreadyMember / ErrAlreadyInvited
raw, hash := auth.NewToken()
_, err := s.Q.CreateOrgInvitation(ctx, sqlcgen.CreateOrgInvitationParams{
ID: auth.NewID(), OrganizationID: o.ID, Email: emailAddr, Role: role,
TokenHash: hash, InvitedBy: inviterID,
ExpiresAt: now().Add(auth.InviteTTL), CreatedAt: now(),
})
// ...
}The token pattern repeats across the whole kit: the email carries raw, the database stores only its SHA-256. A database leak exposes no usable link, and lookups by hash are timing-safe by construction. Timestamps are computed in Go (now() is UTC, truncated) — never now() in SQL, which the two engines spell differently.
5. The query (internal/db/queries/orgs.sql)
sql
-- name: CreateOrgInvitation :one
INSERT INTO org_invitations (id, organization_id, email, role, token_hash, invited_by, expires_at, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING *;sqlc turns this into the typed Go you saw above — once, with $N placeholders. The same generated code runs on PostgreSQL and SQLite (modernc's driver binds $N natively), which is why there is exactly one migration set and one query set for two first-class engines. make sqlc regenerates; nothing is written by hand.
6. The email (internal/org/org.go → internal/email)
go
subject, body, err := email.Render("org_invitation", inviterLocale, map[string]any{
"OrgName": o.Name,
"URL": s.BaseURL + "/invite?token=" + raw,
})Templates are plain text, per locale (templates/fr/org_invitation.txt, templates/en/…), rendered in the invitee's likely language. In production the message goes through the durable job queue — a redeploy cannot swallow it — and a parity test fails the build if a locale is missing a template.
7. Back out: what the invitee does
The emailed link lands on /invite?token=…. Accepting consumes the invitation and creates the membership in one transaction — a rejected acceptance (already a member, expired) rolls back and the invitation survives. If the token never reaches the browser (account created straight from /signup, another machine), the dashboard offers the invitation to the verified address instead. Both paths, and the cross-tenant 404s, are integration-tested against a real database.
That's the whole architecture
Every feature follows the shape you just read:
| Layer | Where | Rule |
|---|---|---|
| Routes | internal/http/server.go | middleware composition is the security model |
| Tenancy | middleware/org.go | resolve through membership; outsiders get 404 |
| Handlers | internal/http/handlers/ | decode, validate, delegate, map errors |
| Domain | internal/{auth,org,user,billing,admin} | plain structs, sentinel errors |
| Queries | internal/db/queries/*.sql | portable SQL, generated once, both engines |
internal/email | text templates × locales, durable queue |
Where to go next:
- Billing (
internal/billing) adds one idea: webhook events and their business writes commit in the same transaction, keyed on the event ID — replays are no-ops. The Stripe guide covers operating it. - Outbound HTTP (
internal/safehttp) is the one client allowed to fetch configurable URLs: SSRF-hardened, private ranges blocked. - Adding your own resource: Make it yours walks the same chain in build order.