The middleware and the nav header
Four files as tabs, in dependency order: middleware.go → nav.go → layout.templ → home.templ (the request plumbing, the header's data model, then the two templates that use it). The last two are modifications.
1. internal/handlers/middleware.go (new) — the three pieces:
func (h *Handlers) LoadUser(next http.Handler) http.Handler { /* cookie → context, never blocks */ }
func (h *Handlers) RequireAuth(next http.Handler) http.Handler { /* no user → redirect /login */ }
func UserFrom(ctx context.Context) (db.User, bool) { /* read the user back out */ }
The user rides the request in a typed context key (userCtxKey), a private ctxKey type so nothing else can collide with it. LoadUser logs a real DB error but still serves the request as anonymous — one flaky query shouldn't 500 the whole site.
2. internal/web/nav.go (new) — a tiny model the header needs:
type Nav struct {
LoggedIn bool
Email string
}
(A CSRFToken field joins it in lesson 6.)
3. internal/web/layout.templ (modify) — the header's static tagline becomes a real nav that branches on nav.LoggedIn:
if nav.LoggedIn {
<a href="/dashboard">Dashboard</a>
<span class="who">{ nav.Email }</span>
<form method="post" action="/logout"><button>Log out</button></form>
} else {
<a href="/login">Log in</a>
<a href="/register">Register</a>
}
Layout now takes a second parameter, nav Nav — so every page threads it through.
4. internal/web/home.templ (modify) — the one consequence of the layout change: Home(userCount int64) becomes Home(nav Nav, userCount int64) and passes nav to @Layout("Home", nav). The page body is untouched.
Nothing new renders yet — no handler builds a nav or calls LoadUser. That's the next step, where login and logout arrive and the header comes alive.