go / handlers

I structure Go web handlers in the www/ package using flat composition rather than heavy constructor patterns. Handlers receive dependencies from the composition root and render HTML templates by converting typed query rows into pre-formatted maps.

Handler Structure

Each feature handler embeds webdeps.Standard, which bundles common runtime necessities (database pool, session helpers, render function):

package admincompanies

import "eds/www/webdeps"

type Handler struct {
	webdeps.Standard
}

Feature handlers are constructed as simple struct literals in the composition root (cmd/www/main.go):

adminCompaniesHandler := &admincompanies.Handler{Standard: std}

PageData to Map Rendering

While the Ruby counterpart enforces a template contract with Data.define structs, Go handlers pass data to templates via map[string]any.

At the boundary to the template engine, maps are required because the rendering signatures are generic. However, the handler remains type-safe because it fetches and maps data using database-backed Go structs before formatting them:

type dbRow struct {
	ID          int64       `db:"id"`
	CompanyName string      `db:"company_name"`
	EdsScore    float64     `db:"eds_score"`
	LastMetOn   pgtype.Date `db:"last_met_on"`
}

func (h *Handler) Index(w http.ResponseWriter, r *http.Request) {
	// 1. Validate params first
	if err := webutil.ValidateParams(r, "query"); err != nil {
		h.WriteError(w, 400, err.Error())
		return
	}

	// 2. Fetch typed rows
	rows, err := h.fetchRows(r.Context())
	if err != nil {
		h.WriteError(w, 500, "database error")
		return
	}

	// 3. Map and format to view dictionary
	viewRows := make([]map[string]any, 0, len(rows))
	for _, row := range rows {
		viewRows = append(viewRows, map[string]any{
			"id":           row.ID,
			"company_name": row.CompanyName,
			"eds_score":    fmt.Sprintf("%.1f", row.EdsScore),
			"last_met_on":  formatDate(row.LastMetOn),
		})
	}

	// 4. Render page
	html, err := h.RenderPage(r, "admin/companies/index", map[string]any{
		"rows": viewRows,
	})
	if err != nil {
		h.WriteError(w, 500, err.Error())
		return
	}
	h.WriteHTML(w, 200, html)
}

Why this pattern

← All articles