trilha

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 5, 2026 License: MIT Imports: 32 Imported by: 0

README

Trilha

ci Go Reference

Framework web para Go com roteamento por arquivos. Layouts aninhados, rotas de API, middleware por pasta, dev server com recarga automática e um único binário de produção. Zero dependências fora da biblioteca padrão. A organização por pastas segue o modelo popularizado pelo Next.js*, traduzido para as convenções do Go.

app/
├── layout.go            → <html> raiz (envolve tudo)
├── page.go              → GET /
├── middleware.go        → roda em toda requisição
├── not_found.go         → página 404
├── error.go             → página 500
├── setup.go             → inicialização (banco, cache...)
├── blog/
│   ├── layout.go        → envolve /blog/**
│   ├── page.go          → GET /blog
│   ├── novo/page.go     → GET /blog/novo  (+ POST do formulário)
│   └── slug_/page.go    → GET /blog/{slug}
├── docs/path__/page.go  → GET /docs/{path...}
├── marketing-/          → grupo: não aparece na URL
│   ├── layout.go        → envolve /precos e /sobre
│   ├── precos/page.go   → GET /precos
│   └── sobre/page.go    → GET /sobre
├── admin/
│   ├── middleware.go    → só para /admin/**
│   └── page.go
└── api/posts/route.go   → GET/POST /api/posts
public/style.css         → servido em /style.css

Documentação

https://emersonjoe.github.io/trilha — trilha "Aprender" (do trilha new ao deploy, com desafios) e "Referência" por pacote. O site é um app Trilha, exportado com trilha export.

Começando

go install github.com/emersonjoe/trilha/cmd/trilha@latest
trilha new meu-app && cd meu-app
trilha dev              # → http://localhost:3000, recarrega ao salvar
trilha build            # → bin/meu-app, com public/ embutido

Ainda não publicado? Use a cópia local: trilha new meu-app --trilha-dir ../trilha.

Convenções

Arquivo Exporta Assinatura
page.go Page func(c *trilha.Ctx) (h.Node, error)
page.go POST, PUT, PATCH, DELETE (opcionais) func(c *trilha.Ctx) error — formulários, com CSRF
route.go GET, POST, PUT, PATCH, DELETE func(c *trilha.Ctx) error — API
layout.go Layout func(c *trilha.Ctx, children h.Node) (h.Node, error)
middleware.go Middleware func(c *trilha.Ctx, next trilha.Next) error
not_found.go (raiz) NotFound func(c *trilha.Ctx) (h.Node, error)
error.go (raiz) Error func(c *trilha.Ctx, err error) (h.Node, error)
setup.go (raiz) Setup func(a *trilha.App) error
setup.go (opcional) Config func(cfg *trilha.Config), antes de trilha.New

Pastas viram segmentos: blog/blog; slug_/{slug}; path__/{path...} (catch-all, precisa ser folha); marketing-grupo de rota: não entra na URL, mas seu layout.go/middleware.go valem para tudo abaixo (o equivalente ao (marketing) do Next.js). [slug] e (grupo) não são válidos em import path do Go, por isso os sufixos _ e -. Pastas iniciadas por _ ou . são ignoradas. Duas pastas que gerem a mesma URL são erro de geração (E_DUPLICATE_ROUTE).

Ordem de execução para GET /admin: middleware(app) → middleware(app/admin) → Page → layout(app/admin)? → layout(app).

Uma página

package sobre

import (
	"github.com/emersonjoe/trilha"
	"github.com/emersonjoe/trilha/h"
)

func Page(c *trilha.Ctx) (h.Node, error) {
	c.SetTitle("Sobre")
	return h.Main(
		h.H1(h.Text("Sobre")),
		h.P(h.Textf("Você é a requisição %s.", c.RequestID())),
	), nil
}

h é um DSL de HTML tipado: elementos e atributos são funções, texto é escapado por padrão e h.Raw é a única porta sem escape. h.If, h.Map e h.Fragment cobrem o fluxo de controle.

Prefere html/template? O pacote tmpl encaixa templates no mesmo pipeline (layouts, título, escape contextual do próprio html/template):

//go:embed relatorio.html
var files embed.FS
var t = tmpl.Must(files, "*.html") // falha na subida, nunca no request

func Page(c *trilha.Ctx) (h.Node, error) {
	return tmpl.Node(t, "relatorio", dados), nil
}

Formulários e APIs

// app/blog/novo/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	return h.Form(h.Method("post"), trilha.CSRFInput(c),
		h.Input(h.Name("titulo")), h.Button(h.Text("Publicar"))), nil
}

func POST(c *trilha.Ctx) error {
	p := posts.Create(c.Form("titulo"), c.Form("corpo"))
	return c.Redirect("/blog/" + p.Slug) // 303: POST → redirect → GET
}

// app/api/posts/route.go
func GET(c *trilha.Ctx) error { return c.JSON(200, posts.All()) }
func POST(c *trilha.Ctx) error {
	var in struct{ Title string `json:"title"` }
	if err := c.BindJSON(&in); err != nil { return err } // 400 / 413
	return c.JSON(201, posts.Create(in.Title, ""))
}

Erros são valores: trilha.ErrNotFound → 404 (HTML ou JSON conforme a rota), trilha.Redirect(url) → 303, trilha.Errorf(422, "...") → status com mensagem, qualquer outro error → 500 com stack só em dev. Métodos não exportados respondem 405 com Allow.

Middleware

// app/admin/middleware.go
func Middleware(c *trilha.Ctx, next trilha.Next) error {
	if ck, err := c.Cookie("session"); err != nil || ck.Value != "ok" {
		return trilha.RedirectCode("/login", 302)
	}
	c.Set("user", "admin") // páginas leem com c.Get("user")
	return next()
}

Interface

Projetos novos vêm com o kit ui: componentes tipados (ui.Button, ui.Card, ui.Field, ui.Tabs, ui.Dialog...) sobre um CSS prefixado e um JS de 200 linhas, ambos copiados para public/ e seus para editar. O tema usa as mesmas variáveis do shadcn/ui (MIT): cole um tema pronto em public/ui.theme.css e nada em Go muda. trilha ui atualiza o kit sem tocar no seu tema.

ui.Card(
	ui.CardHeader(ui.CardTitle("Novo post")),
	ui.CardContent(h.Form(h.Method("post"), trilha.CSRFInput(c),
		ui.Field("titulo", "Título", ui.Input(h.ID("titulo"), h.Name("titulo"), h.Required())),
		ui.Submit(h.Text("Publicar")))),
)

Exemplos

Nível Pasta Ensina
Básico examples/blog convenções, layouts, API, middleware, sessão
Médio examples/cadastro formulário com regras: campos condicionais, validação por campo (c.Bind, trilha.FieldErrors, c.Render), seleção dependente, aviso que some
Complexo examples/orcamento plano de contas em árvore, drill-down, componentes recursivos, diálogo, CSV
IA examples/assistente chat em streaming, agente com ferramentas, MCP

IA e agentes

ai fala o protocolo de chat da OpenAI (funciona com OpenAI, Groq, Mistral, OpenRouter, Ollama, LM Studio, vLLM...), com ferramentas tipadas, agentes, handoffs e streaming; ai/mcp usa e expõe ferramentas pelo Model Context Protocol. Tudo sem dependências externas.

clima := ai.NewTool("clima", "Temperatura em uma cidade.",
    ai.Schema(`{"type":"object","properties":{"cidade":{"type":"string"}},"required":["cidade"]}`),
    ai.Typed(func(ctx context.Context, in struct{ Cidade string }) (string, error) {
        return buscarTemperatura(ctx, in.Cidade)
    }))
agente := &ai.Agent{Name: "Assistente", Instructions: "Responda em português.", Tools: []*ai.Tool{clima}}
res, err := ai.Run(ctx, ai.NewFromEnv(), agente, "Está frio em Curitiba?")

Veja examples/assistente (chat em streaming com c.Stream(), handoff para um tradutor e servidor MCP em /mcp) e o capítulo IA e agentes.

Como funciona

trilha gen varre app/ com go/ast e escreve trilha_gen.go (commitado): um package main que importa cada pacote de rota e chama a.Register(...) com tipos verificados pelo compilador. Nada de reflect, nada de mágica em runtime; go build . funciona sem a CLI. O roteador é o http.ServeMux do Go 1.22+.

trilha dev escuta em :3000, compila o app numa porta interna, faz proxy e injeta um script de live-reload (SSE). Ao salvar: regenera, recompila, troca o processo e avisa o navegador — cerca de 1 s no exemplo. Mudanças só em public/ não recompilam: o navegador recarrega em dezenas de milissegundos. Erro de compilação vira uma página com a saída do go build que some sozinha quando você corrige.

Segurança por padrão: escape de HTML, nosniff/X-Frame-Options/Referrer-Policy, limite de corpo (1 MiB), CSRF por double-submit cookie em formulários, estáticos sem path traversal, logs slog sem corpo nem cookies.

Fora do escopo (por enquanto)

Componentes cliente/hidratação e rotas paralelas. Interatividade no cliente fica em public/*.js (ou htmx).

Licença

MIT (LICENSE). Os arquivos do spec-kit em .specify/ e .claude/skills são MIT da GitHub, Inc.; veja THIRD_PARTY_NOTICES.md.

* Next.js é marca da Vercel, Inc. O Trilha é um projeto independente, sem afiliação, e não contém código do Next.js.

Contribuições são bem-vindas: veja CONTRIBUTING.md, o código de conduta, a política de segurança e a governança. Mudanças de comportamento seguem o fluxo spec-kit em specs/.

Desenvolvimento

make test        # gofmt + vet + go test ./... (inclui e2e da CLI e o exemplo)
make dev-example # trilha dev em examples/blog
make reload      # mede o ciclo editar→ver

Projeto guiado por spec-kit: veja specs/ (001 núcleo, 002 grupos/templates/estáticos, 003 export, 004 segurança, 005 IA) e .specify/memory/constitution.md.


English

Trilha is a file-based web framework for Go (routing conventions inspired by Next.js, no affiliation): routes live under app/ (page.go, route.go, layout.go, middleware.go), nested layouts, typed HTML DSL, CSRF-protected forms, a dev server with live reload and a single production binary with public/ embedded. Dynamic segments use name_ (/{name}) and name__ (/{name...}) because [name] is not a valid Go import path; name- is a route group (Next's (name)). Prefer templates? tmpl.Node(t, "name", data) plugs html/template into the same pipeline. Standard library only. Run trilha new app && cd app && trilha dev.

Documentation

Overview

Package trilha is a file-based web framework for Go: pages, layouts, API routes and middleware are discovered from the app/ directory tree by the trilha CLI, which generates a typed registration file; this package is the runtime those generated files call into.

Index

Constants

View Source
const CSRFCookie = "trilha_csrf"

CSRFCookie is the name of the double-submit cookie.

View Source
const CSRFField = "_csrf"

CSRFField is the hidden form field name.

View Source
const CSRFHeader = "X-CSRF-Token"

CSRFHeader is the header accepted instead of the form field.

View Source
const MinSecretLen = 32

MinSecretLen is the minimum accepted secret size in bytes.

View Source
const NoTimeout time.Duration = -1

NoTimeout disables a Timeouts field (becomes 0 in http.Server).

View Source
const Off = "off"

Off disables a security header when assigned to its field.

Variables

View Source
var BindInvalid = "valor inválido"

BindInvalid is the FieldErrors message Bind records when a value cannot be converted to the field's type. Change it to localise.

View Source
var ErrNoSecret = errors.New("trilha: TRILHA_SECRET não definido; cookies assinados indisponíveis")

ErrNoSecret is returned by SetSigned when no secret is configured.

View Source
var ErrNotFound = errors.New("trilha: not found")

ErrNotFound makes the framework respond with 404 using the app's not-found page (HTML routes) or a JSON error (API routes).

View Source
var ErrRateLimited = &HTTPError{Code: http.StatusTooManyRequests, Message: "too many requests"}

ErrRateLimited is the 429 error; handlers may return it themselves.

Functions

func CSRFInput

func CSRFInput(c *Ctx) h.Node

CSRFInput renders the hidden input for forms: h.Form(..., trilha.CSRFInput(c), ...).

func CompileErrorPage

func CompileErrorPage(output string) string

compileErrorPage is used by the CLI dev server; exported for reuse.

func Errorf

func Errorf(code int, format string, a ...any) error

Errorf builds an HTTPError with a formatted client-visible message.

func Fatal

func Fatal(err error)

Fatal logs a fatal error and exits, ignoring the normal server-closed error.

func NonceAttr added in v0.2.0

func NonceAttr(c *Ctx) h.Node

NonceAttr renders the nonce attribute for an inline <script>: h.Script(trilha.NonceAttr(c), h.Raw(js)).

func PublicFS

func PublicFS(embedded fs.FS, dir string) fs.FS

PublicFS returns the static file system for the public directory: the embedded copy in prod, the on-disk directory in dev (so edits show up without a rebuild).

func Redirect

func Redirect(url string) error

Redirect returns a 303 See Other redirect error (POST → redirect → GET).

func RedirectCode

func RedirectCode(url string, code int) error

RedirectCode returns a redirect error with a custom 3xx status.

func Run added in v0.2.0

func Run(a *App)

Run serves the app, or exports it when TRILHA_EXPORT names a directory. The generated main calls this.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is a configured Trilha application.

func New

func New(cfg Config) *App

New creates an App. Zero values in cfg receive defaults.

func (*App) AddExportPath added in v0.2.0

func (a *App) AddExportPath(paths ...string)

AddExportPath registers extra paths to render on Export, typically pages under dynamic routes (e.g. "/blog/ola"). Call it from Setup.

func (*App) BasePath added in v0.2.0

func (a *App) BasePath() string

BasePath returns the URL prefix the app is served under ("" or "/docs").

func (*App) Config added in v0.2.0

func (a *App) Config() *Config

Config returns the live configuration for adjustment in Setup. Every field may be changed there: per-request fields (Security, Public, MaxBodyBytes, CSRFForAPI, BasePath, OnSecurityEvent, Static*) are read on each request; derived fields (Logger, Secret/PreviousSecret, RateLimit, TrustedProxies) are reapplied when serving starts (ListenAndServe, Handler, Export); Addr and Timeouts are read by ListenAndServe. To build the Config before New, export func Config(cfg *trilha.Config) in app/setup.go.

func (*App) Env

func (a *App) Env() Env

Env returns the runtime environment.

func (*App) Export added in v0.2.0

func (a *App) Export(dir string) error

Export renders the app as a static site into dir: one index.html per path, 404.html from the not-found page, and a copy of public/. The directory is emptied first, but only if Export created it before.

func (*App) ExportPaths added in v0.2.0

func (a *App) ExportPaths() []string

ExportPaths lists what Export will render: static page routes plus the paths added with AddExportPath, sorted and deduplicated.

func (*App) Handler

func (a *App) Handler() http.Handler

Handler returns the root http.Handler (useful for tests and embedding). Like ListenAndServe, it reapplies Config changes made in Setup.

func (*App) ListenAndServe

func (a *App) ListenAndServe() error

ListenAndServe serves until SIGINT/SIGTERM, then shuts down gracefully.

func (*App) Logger

func (a *App) Logger() *slog.Logger

Logger returns the app logger.

func (*App) OnShutdown added in v0.3.0

func (a *App) OnShutdown(fn func(*App) error)

OnShutdown registers fn to run after the server stopped accepting requests (close pools, flush logs). Hooks run in reverse registration order; setup.go may export func Shutdown(a *trilha.App) error, which the generated main registers for you.

func (*App) Register

func (a *App) Register(r Route)

Register adds a route. It is normally called only by trilha_gen.go.

func (*App) Routes

func (a *App) Routes() map[string][]string

Routes lists registered patterns (sorted) with their methods.

func (*App) Security added in v0.2.0

func (a *App) Security() *Security

Security returns the security settings for adjustment in Setup.

func (*App) SetErrorPage

func (a *App) SetErrorPage(e ErrorPageFunc)

SetErrorPage sets the page rendered on 500 (app/error.go).

func (*App) SetNotFound

func (a *App) SetNotFound(p PageFunc)

SetNotFound sets the page rendered on 404 (app/not_found.go).

func (*App) SetRootLayout

func (a *App) SetRootLayout(l LayoutFunc)

SetRootLayout sets the layout used by the not-found and error pages.

func (*App) Values

func (a *App) Values() map[string]any

Values is a process-wide bag filled by Setup (database pools, caches...). Prefer package-level variables in your own packages; this exists for glue.

type Config

type Config struct {
	// Addr is the listen address (default ":3000").
	Addr string
	// Env selects dev (stack traces, live reload, no static cache) or prod.
	Env Env
	// MaxBodyBytes limits request bodies (default 1 MiB).
	MaxBodyBytes int64
	// Logger receives structured request logs (default slog.Default()).
	Logger *slog.Logger
	// Public serves static files at the root. nil disables static files.
	Public fs.FS
	// CSRFForAPI also enforces CSRF tokens on route.go handlers.
	CSRFForAPI bool
	// BasePath is the URL prefix the app is served under (e.g. "/docs" on
	// GitHub Pages). Read it with Ctx.Base when building links.
	BasePath string
	// Security tunes the hardening headers (zero value = defaults).
	Security Security
	// TrustedProxies lists CIDRs whose X-Forwarded-For/Proto are honoured.
	TrustedProxies []string
	// RateLimit enables a global per-client limit (zero = off).
	RateLimit RateLimit
	// Secret signs cookies (TRILHA_SECRET); PreviousSecret still verifies.
	Secret, PreviousSecret []byte
	// Timeouts protect the server from slow clients.
	Timeouts Timeouts
	// StaticCacheControl replaces the production Cache-Control of files in
	// Public (default "public, max-age=3600"; dev always sends no-cache).
	StaticCacheControl string
	// StaticHeaders runs for every file served from Public, after the
	// defaults, and may set any header (immutable for hashed assets, CORP...).
	StaticHeaders func(name string, hdr http.Header)
	// OnSecurityEvent is called for blocked requests (CSRF, 401/403, 413, 429, panic).
	OnSecurityEvent func(SecurityEvent)
	// DevReload controls the live-reload script injected in Dev pages; Off
	// disables it (snapshot tests, HTML diffs). TRILHA_DEV_RELOAD=off does the same.
	DevReload string
}

Config configures an App.

func ConfigFromEnv

func ConfigFromEnv() Config

ConfigFromEnv builds a Config from ADDR/PORT and TRILHA_ENV.

type Ctx

type Ctx struct {
	// contains filtered or unexported fields
}

Ctx wraps one request/response pair. It is created per request and is not safe for use from other goroutines after the handler returns.

func (*Ctx) App

func (c *Ctx) App() *App

App returns the application.

func (*Ctx) Base added in v0.2.0

func (c *Ctx) Base() string

Base returns the app's base path for building links: c.Base()+"/aprender".

func (*Ctx) Bind added in v0.5.0

func (c *Ctx) Bind(v any) error

Bind fills a struct from the request: JSON when the Content-Type is application/json (see BindJSON), otherwise form fields and query string. Fields are matched by the `form:"name"` tag (or the field name). Supported types: string, []string, bool (checkbox: on/true/1), int, int64, float64, time.Time (2006-01-02 or 2006-01-02T15:04) and pointers to them (nil when absent). A nested struct is flattened: its fields are read with the struct's tag as prefix (`Cobranca Endereco `+"`"+`form:"cob_"`+"`"+` reads cob_cep...), or with no prefix when it has no tag. Values that fail to convert are reported as FieldErrors, after every field was tried, so all messages reach the user at once.

var in struct {
	Nome  string  `form:"nome"`
	Idade int     `form:"idade"`
	Ativo bool    `form:"ativo"`
}
if err := c.Bind(&in); err != nil { return err }

func (*Ctx) BindJSON

func (c *Ctx) BindJSON(v any) error

BindJSON decodes the request body into v. Returns an HTTPError 400 on malformed JSON and 413 when the body exceeds the limit.

func (*Ctx) CSRFToken

func (c *Ctx) CSRFToken() string

CSRFToken returns the request's CSRF token, creating the cookie on first use. Put it in forms with CSRFInput or send it in the X-CSRF-Token header.

func (*Ctx) ClearCookie added in v0.2.0

func (c *Ctx) ClearCookie(name string)

ClearCookie expires a cookie.

func (*Ctx) ClientIP added in v0.2.0

func (c *Ctx) ClientIP() string

ClientIP returns the client address: RemoteAddr, or the right-most untrusted entry of X-Forwarded-For when the peer is a trusted proxy.

func (*Ctx) Context

func (c *Ctx) Context() context.Context

Context returns the request context.

func (*Ctx) Cookie

func (c *Ctx) Cookie(name string) (*http.Cookie, error)

Cookie returns a request cookie.

func (*Ctx) Env

func (c *Ctx) Env() Env

Env returns the runtime environment.

func (*Ctx) Form

func (c *Ctx) Form(name string) string

Form returns a form field (POST body or query string). Returns "" if the form cannot be parsed; use FormErr to distinguish.

func (*Ctx) FormErr

func (c *Ctx) FormErr() error

FormErr returns the error from parsing the form, if any (413 or 400).

func (*Ctx) Get

func (c *Ctx) Get(key string) any

Get reads a per-request value; nil when absent.

func (*Ctx) HTML

func (c *Ctx) HTML(code int, n h.Node) error

HTML renders a node as the whole response, without layouts.

func (*Ctx) Header

func (c *Ctx) Header(k, v string)

Header sets a response header.

func (*Ctx) JSON

func (c *Ctx) JSON(code int, v any) error

JSON writes a JSON response.

func (*Ctx) NoWriteDeadline added in v0.2.0

func (c *Ctx) NoWriteDeadline() error

NoWriteDeadline disables the server write timeout for this response; call it before streaming (SSE, long downloads).

func (*Ctx) Nonce added in v0.2.0

func (c *Ctx) Nonce() string

Nonce returns the per-request CSP nonce for inline scripts.

func (*Ctx) Param

func (c *Ctx) Param(name string) string

Param returns a path parameter ({slug} or {path...}).

func (*Ctx) Query

func (c *Ctx) Query(name string) string

Query returns the first value of a query-string parameter.

func (*Ctx) Redirect

func (c *Ctx) Redirect(url string) error

Redirect returns a redirect error (303). Use as `return c.Redirect("/x")`.

func (*Ctx) Render added in v0.5.0

func (c *Ctx) Render(code int, node h.Node) error

Render writes node as a page with the route's layouts applied (innermost first), like GET does. Use it in POST handlers to answer a form with validation errors inside the same layouts:

if errs := validate(in); errs.Any() {
	return c.Render(422, formulario(c, in, errs))
}

func (*Ctx) Request

func (c *Ctx) Request() *http.Request

Request returns the underlying *http.Request.

func (*Ctx) RequestID

func (c *Ctx) RequestID() string

RequestID returns the X-Request-ID header or a generated id.

func (*Ctx) Set

func (c *Ctx) Set(key string, v any)

Set stores a per-request value (typically from middleware).

func (*Ctx) SetContext added in v0.2.0

func (c *Ctx) SetContext(ctx context.Context)

SetContext replaces the request context, so a middleware can pass values to code that only receives *http.Request (templates, stdlib helpers).

func (*Ctx) SetCookie

func (c *Ctx) SetCookie(ck *http.Cookie)

SetCookie adds a Set-Cookie header.

func (*Ctx) SetRequest added in v0.2.0

func (c *Ctx) SetRequest(r *http.Request)

SetRequest replaces the request (rewritten URL, wrapped body...). Values already read from the old request (form, request id) are kept.

func (*Ctx) SetSigned added in v0.2.0

func (c *Ctx) SetSigned(name, value string, ttl time.Duration) error

SetSigned stores a tamper-proof cookie (HttpOnly, SameSite=Lax, Secure on HTTPS) that expires after ttl. Returns ErrNoSecret without a secret.

func (*Ctx) SetTitle

func (c *Ctx) SetTitle(t string)

SetTitle sets the page title; layouts read it with Title.

func (*Ctx) Signed added in v0.2.0

func (c *Ctx) Signed(name string) (value string, ok bool)

Signed reads a cookie written by SetSigned; ok is false when missing, tampered or expired.

func (*Ctx) Status

func (c *Ctx) Status(code int)

Status sets the status code used by the next page render.

func (*Ctx) Stream added in v0.2.0

func (c *Ctx) Stream() *Stream

Stream switches the response to text/event-stream, disables the write deadline and returns a writer for events. Use it from a GET route:

func GET(c *trilha.Ctx) error {
	s := c.Stream()
	for chunk := range chunks {
		if err := s.Send("delta", chunk); err != nil { return err }
	}
	return s.Send("done", "")
}

Compression is skipped for streams, and each event is flushed right away.

func (*Ctx) Text

func (c *Ctx) Text(code int, s string) error

Text writes a plain-text response.

func (*Ctx) Title

func (c *Ctx) Title() string

Title returns the page title set by the page (for layouts).

func (*Ctx) Writer

func (c *Ctx) Writer() http.ResponseWriter

Writer returns the underlying http.ResponseWriter.

func (*Ctx) Written

func (c *Ctx) Written() bool

Written reports whether the response has already started.

type Env

type Env string

Env is the runtime environment.

const (
	Dev  Env = "dev"
	Prod Env = "prod"
)

type ErrorPageFunc

type ErrorPageFunc func(*Ctx, error) (h.Node, error)

ErrorPageFunc renders the 500 page (error.go: Error).

type FieldErrors added in v0.5.0

type FieldErrors map[string]string

FieldErrors collects validation messages by form field. Returned from a handler it answers 422: JSON with a "fields" object on API routes, or the error page on pages — though a form usually re-renders itself with Ctx.Render and shows each message next to its field.

func (FieldErrors) Add added in v0.5.0

func (e FieldErrors) Add(field, msg string)

Add records a message for field (the first one wins).

func (FieldErrors) Any added in v0.5.0

func (e FieldErrors) Any() bool

Any reports whether there is at least one error.

func (FieldErrors) Error added in v0.5.0

func (e FieldErrors) Error() string

Error joins the messages, sorted by field, for logs and API responses.

func (FieldErrors) Get added in v0.5.0

func (e FieldErrors) Get(field string) string

Get returns the message for field, or "".

func (FieldErrors) Has added in v0.5.0

func (e FieldErrors) Has(field string) bool

Has reports whether field has an error.

func (FieldErrors) OrNil added in v0.5.0

func (e FieldErrors) OrNil() error

OrNil returns e as an error, or nil when empty, for `return errs.OrNil()`.

type HTTPError

type HTTPError struct {
	Code    int
	Message string
}

HTTPError carries an HTTP status and a message safe to show to the client.

func (*HTTPError) Error

func (e *HTTPError) Error() string

type HandlerFunc

type HandlerFunc func(*Ctx) error

HandlerFunc handles an API method or a form submission.

type LayoutFunc

type LayoutFunc func(*Ctx, h.Node) (h.Node, error)

LayoutFunc wraps rendered children (layout.go: Layout).

type MiddlewareFunc

type MiddlewareFunc func(*Ctx, Next) error

MiddlewareFunc intercepts a subtree (middleware.go: Middleware).

func Limit added in v0.2.0

func Limit(rps float64, burst int) MiddlewareFunc

Limit returns a middleware applying its own per-client limit to a subtree: put `var limit = trilha.Limit(2, 5)` in middleware.go and call it.

type Next

type Next func() error

Next continues the middleware chain.

type PageFunc

type PageFunc func(*Ctx) (h.Node, error)

PageFunc renders a page (page.go: Page).

type RateLimit added in v0.2.0

type RateLimit struct {
	// RPS is the sustained requests per second per client.
	RPS float64
	// Burst is the bucket size (requests allowed at once).
	Burst int
}

RateLimit configures the per-client token bucket. Zero disables it.

type RedirectError

type RedirectError struct {
	URL  string
	Code int
}

RedirectError is returned by handlers to redirect the client.

func (*RedirectError) Error

func (e *RedirectError) Error() string

type Route

type Route struct {
	// Pattern is the path pattern, e.g. "/blog/{slug}" or "/docs/{path...}".
	Pattern string
	// Page renders GET for page routes; nil for API routes.
	Page PageFunc
	// Methods maps HTTP methods to handlers (route.go, or form methods in page.go).
	Methods map[string]HandlerFunc
	// Layouts wrap the page, innermost first.
	Layouts []LayoutFunc
	// Middlewares run before the handler, outermost first.
	Middlewares []MiddlewareFunc
	// Kind decides how errors are rendered (HTML page or JSON) and whether
	// CSRF applies. KindAuto: page.go routes are pages; route.go routes are
	// APIs, except that a browser navigation (Accept: text/html, outside
	// /api/) gets HTML error pages. route.go may export `var Kind = trilha.KindPage`.
	Kind RouteKind
}

Route is one entry produced by the generator for App.Register.

type RouteKind added in v0.3.0

type RouteKind int

RouteKind is the error/CSRF behaviour of a Route; see Route.Kind.

const (
	// KindAuto derives the kind from the files (page.go → page, route.go → API).
	KindAuto RouteKind = iota
	// KindPage renders errors as HTML pages and enforces CSRF on body methods.
	KindPage
	// KindAPI renders errors as JSON, whatever the Accept header says.
	KindAPI
)

type Security added in v0.2.0

type Security struct {
	// CSP is the Content-Security-Policy. Empty = default policy with a
	// per-request nonce for scripts; the text may contain {nonce}.
	CSP string
	// CSPExtra adds sources to directives of the default policy, e.g.
	// {"style-src": {"https://fonts.googleapis.com"}}.
	CSPExtra map[string][]string
	// HSTS is sent only over HTTPS (TLS or a trusted proxy saying so).
	HSTS string
	// PermissionsPolicy restricts browser features.
	PermissionsPolicy string
	// COOP is Cross-Origin-Opener-Policy.
	COOP string
	// FrameOptions is X-Frame-Options.
	FrameOptions string
	// Referrer is Referrer-Policy.
	Referrer string
}

Security configures the hardening headers sent with every response. The zero value means "defaults"; set a field to Off to drop that header.

type SecurityEvent added in v0.2.0

type SecurityEvent struct {
	// Kind is one of csrf, auth, body, rate, panic.
	Kind      string
	Status    int
	Method    string
	Path      string
	IP        string
	RequestID string
}

SecurityEvent describes a request the framework blocked or flagged.

type Signer added in v0.2.0

type Signer struct {
	// contains filtered or unexported fields
}

Signer signs and verifies values with HMAC-SHA256. The first key signs; every key verifies, so a previous key can be kept during rotation.

func NewSigner added in v0.2.0

func NewSigner(keys ...[]byte) *Signer

NewSigner creates a signer; the first key signs, the rest only verify.

func (*Signer) Sign added in v0.2.0

func (s *Signer) Sign(value string, exp time.Time) (string, error)

Sign returns value|expiry|mac, safe for a cookie.

func (*Signer) Verify added in v0.2.0

func (s *Signer) Verify(token string, now time.Time) (string, bool)

Verify checks the signature and expiry with any key, returning the value.

type Stream added in v0.2.0

type Stream struct {
	// contains filtered or unexported fields
}

Stream is a Server-Sent Events writer returned by Ctx.Stream.

func (*Stream) Comment added in v0.2.0

func (s *Stream) Comment(text string) error

Comment sends a comment line (keeps proxies from timing out the stream).

func (*Stream) Done added in v0.2.0

func (s *Stream) Done() <-chan struct{}

Done reports when the client went away.

func (*Stream) Flush added in v0.2.0

func (s *Stream) Flush()

Flush pushes buffered bytes to the client.

func (*Stream) JSON added in v0.2.0

func (s *Stream) JSON(event string, v any) error

JSON marshals v and sends it as the event data.

func (*Stream) Send added in v0.2.0

func (s *Stream) Send(event, data string) error

Send writes one event. An empty name sends an unnamed message (received by EventSource.onmessage). Multi-line data is split into several data: lines.

type Timeouts added in v0.2.0

type Timeouts struct {
	ReadHeader     time.Duration // 10s
	Read           time.Duration // 30s
	Write          time.Duration // 60s (use Ctx.NoWriteDeadline for streams)
	Idle           time.Duration // 120s
	MaxHeaderBytes int           // 64 KiB
	// Shutdown is how long ListenAndServe waits for in-flight requests after
	// SIGINT/SIGTERM before closing (5s).
	Shutdown time.Duration
}

Timeouts are the http.Server limits. Zero fields get defaults; NoTimeout disables one (large uploads, long polls). Write applies to the whole response: streams should call Ctx.Stream or Ctx.NoWriteDeadline instead of disabling it globally.

Directories

Path Synopsis
ai
Package ai talks to chat-completion APIs that follow the OpenAI protocol (OpenAI, Azure, Ollama, LM Studio, Groq, OpenRouter, vLLM...) and builds agents on top of them: tools, handoffs, parallel and chained agents.
Package ai talks to chat-completion APIs that follow the OpenAI protocol (OpenAI, Azure, Ollama, LM Studio, Groq, OpenRouter, vLLM...) and builds agents on top of them: tools, handoffs, parallel and chained agents.
mcp
Package mcp implements the Model Context Protocol (JSON-RPC 2.0 over stdio or Streamable HTTP) as a client, to bring external tools into an ai.Agent, and as a server, to expose your app's tools to MCP hosts.
Package mcp implements the Model Context Protocol (JSON-RPC 2.0 over stdio or Streamable HTTP) as a client, to bring external tools into an ai.Agent, and as a server, to expose your app's tools to MCP hosts.
cmd
trilha command
Command trilha is the CLI: new, gen, dev, build, routes.
Command trilha is the CLI: new, gen, dev, build, routes.
examples
assistente command
assistente/app/api/chat
Package chat streams agent runs to the browser over Server-Sent Events.
Package chat streams agent runs to the browser over Server-Sent Events.
assistente/app/mcp
Package mcp exposes the example's tools to MCP hosts (Streamable HTTP).
Package mcp exposes the example's tools to MCP hosts (Streamable HTTP).
assistente/internal/ferramentas
Package ferramentas holds the tools, agents and MCP server of the example.
Package ferramentas holds the tools, agents and MCP server of the example.
blog command
blog/app/api/posts
Package posts exposes the JSON API at /api/posts.
Package posts exposes the JSON API at /api/posts.
blog/app/marketing-
Package marketing is a route group: its layout wraps /precos and /sobre without adding a URL segment (folder name ends with "-").
Package marketing is a route group: its layout wraps /precos and /sobre without adding a URL segment (folder name ends with "-").
blog/app/painel-
Package painel is a route group for the app area (/painel, /relatorio).
Package painel is a route group for the app area (/painel, /relatorio).
blog/app/painel-/relatorio
Package relatorio renders a page from an html/template file instead of the h DSL, using the tmpl adapter.
Package relatorio renders a page from an html/template file instead of the h DSL, using the tmpl adapter.
blog/internal/posts
Package posts is an in-memory post store for the example app.
Package posts is an in-memory post store for the example app.
cadastro command
cadastro/app/api/cidades
Package cidades serves the dependent select: GET /api/cidades?uf=SP.
Package cidades serves the dependent select: GET /api/cidades?uf=SP.
cadastro/internal/clientes
Package clientes holds the domain of the example: the form model, its validation rules and an in-memory store.
Package clientes holds the domain of the example: the form model, its validation rules and an in-memory store.
orcamento command
orcamento/app/api/relatorio.csv
Package relatoriocsv exports the month as CSV at /api/relatorio.csv?mes=.
Package relatoriocsv exports the month as CSV at /api/relatorio.csv?mes=.
orcamento/app/lancamentos
Package lancamentos has the standalone entry page (works without JS) and the POST every entry form submits to (dialog included).
Package lancamentos has the standalone entry page (works without JS) and the POST every entry form submits to (dialog included).
orcamento/internal/componentes
Package componentes holds the reusable, nested UI pieces of the budget example.
Package componentes holds the reusable, nested UI pieces of the budget example.
orcamento/internal/plano
Package plano is the domain of the budget example: a chart of accounts (tree), monthly budgets on analytic accounts, and entries.
Package plano is the domain of the budget example: a chart of accounts (tree), monthly budgets on analytic accounts, and entries.
h
Package h is a small, dependency-free HTML DSL: every element, attribute and piece of text is a Node that knows how to render itself to an io.Writer.
Package h is a small, dependency-free HTML DSL: every element, attribute and piece of text is a Node that knows how to render itself to an io.Writer.
internal
dev
Package dev implements `trilha dev`: a polling file watcher, a builder and a supervisor that runs the app behind a reverse proxy with live reload.
Package dev implements `trilha dev`: a polling file watcher, a builder and a supervisor that runs the app behind a reverse proxy with live reload.
gen
Package gen turns a scan.Result into the source of trilha_gen.go.
Package gen turns a scan.Result into the source of trilha_gen.go.
scaffold
Package scaffold writes a new project from embedded templates.
Package scaffold writes a new project from embedded templates.
scan
Package scan walks an app/ directory and turns its file conventions into a list of routes, validating them along the way.
Package scan walks an app/ directory and turns its file conventions into a list of routes, validating them along the way.
app
Package app is the documentation site of Trilha, built with Trilha.
Package app is the documentation site of Trilha, built with Trilha.
internal/demos
Package demos holds the "código → resultado" examples used by the home page and by chapters.
Package demos holds the "código → resultado" examples used by the home page and by chapters.
internal/docs
Package docs loads the Markdown content of the documentation site, builds the navigation and renders pages.
Package docs loads the Markdown content of the documentation site, builds the navigation and renders pages.
internal/md
Package md is a deliberately small Markdown-to-HTML converter for the documentation site: headings with ids, paragraphs, lists, fenced code (with Go highlighting), tables, blockquotes, callouts (:::nome), inline code, emphasis and links.
Package md is a deliberately small Markdown-to-HTML converter for the documentation site: headings with ids, paragraphs, lists, fenced code (with Go highlighting), tables, blockquotes, callouts (:::nome), inline code, emphasis and links.
internal/ui
Package ui holds the shared building blocks of the documentation site: the document shell, sidebar, table of contents and helpers.
Package ui holds the shared building blocks of the documentation site: the document shell, sidebar, table of contents and helpers.
Package tmpl adapts html/template to the h.Node pipeline, for developers who prefer template files over the Go DSL.
Package tmpl adapts html/template to the h.Node pipeline, for developers who prefer template files over the Go DSL.
Package ui is Trilha's default, customizable UI kit: typed components that render classes consumed by public/ui.css, plus a small ui.js for behavior.
Package ui is Trilha's default, customizable UI kit: typed components that render classes consumed by public/ui.css, plus a small ui.js for behavior.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL