Ir para o conteúdo
Trilha

Páginas Go a partir de pastas.

Trilha é um framework web para Go: crie um arquivo em app/ e ele vira uma rota. Layouts aninhados, formulários, APIs, middleware, servidor de desenvolvimento com recarga e um binário só. Nada além da biblioteca padrão.

go install github.com/emersonjoe/trilha/cmd/trilha@latest
trilha new agenda && cd agenda && trilha dev

Uma pasta, uma rota

Não existe tabela de rotas. A árvore de app/ é a tabela: page.go responde uma página, route.go responde JSON, layout.go envolve tudo que está abaixo e middleware.go intercepta a subárvore. Segmentos dinâmicos usam o sufixo _ e grupos de rota o sufixo -, porque [slug] não é um caminho de import válido em Go.

app/
├── layout.go             ← <html> de todas as páginas
├── page.go               ← GET /
├── eventos/
│   ├── layout.go         ← envolve /eventos/**
│   ├── page.go           ← GET /eventos
│   ├── novo/page.go      ← GET /eventos/novo + POST do formulário
│   └── slug_/page.go     ← GET /eventos/{slug}
├── organizador-/         ← grupo: não entra na URL
│   ├── middleware.go     ← exige login
│   └── painel/page.go    ← GET /painel
└── api/eventos/route.go  ← GET e POST /api/eventos

HTML em Go, com escape por padrão

O pacote h é um DSL tipado: elementos e atributos são funções, h.Map e h.If cobrem o fluxo de controle e todo texto é escapado. O resultado ao lado não foi escrito à mão: é o código executado durante a construção deste site.

app/…/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	return h.Ul(h.Class("eventos"),
		h.Map(eventos, func(e Evento) h.Node {
			return h.Li(
				h.Strong(h.Text(e.Nome)),
				h.Textf(" · %s · ", e.Cidade),
				h.IfElse(e.Vagas > 0,
					h.Textf("%d vagas", e.Vagas),
					h.Em(h.Text("lotado"))),
			)
		}),
	), nil
}
resultado
  • Encontro Go Campinas · Campinas · 12 vagas
  • Oficina de HTTP · Recife · lotado
  • Noite de código · Curitiba · 4 vagas
app/…/page.go
nome := "<script>alert('oi')</script>"

return h.P(
	h.Text("Olá, "),
	h.Strong(h.Text(nome)),
), nil
resultado

Olá, <script>alert('oi')</script>

Layouts que se encaixam

Cada pasta pode ter um layout.go. A página é renderizada primeiro e passa para o layout mais interno, que passa para o de fora, até o <html> raiz. O título definido pela página com c.SetTitle chega a todos eles.

app/…/page.go
// app/eventos/layout.go
func Layout(c *trilha.Ctx, children h.Node) (h.Node, error) {
	return h.Section(h.Class("agenda"),
		h.Nav(h.A(h.Href("/eventos"), h.Text("Todos")),
		      h.A(h.Href("/eventos/novo"), h.Text("Novo"))),
		children,
	), nil
}

// app/eventos/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	c.SetTitle("Eventos")
	return h.H2(h.Text("Próximos eventos")), nil
}
resultado

Próximos eventos

Formulários e APIs sem cerimônia

Exporte POST no mesmo page.go e o formulário chega com token CSRF verificado. Em route.go, cada método HTTP é uma função. Erros são valores: trilha.ErrNotFound vira 404, c.Redirect vira 303, qualquer outro erro vira 500 com stack só em desenvolvimento.

app/…/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	return h.Form(h.Method("post"), h.Action("/eventos/novo"),
		trilha.CSRFInput(c),
		h.Label(h.For("nome"), h.Text("Nome do evento")),
		h.Input(h.ID("nome"), h.Name("nome"), h.Required()),
		h.Button(h.Type("submit"), h.Text("Publicar")),
	), nil
}

func POST(c *trilha.Ctx) error {
	ev := agenda.Criar(c.Form("nome"))
	return c.Redirect("/eventos/" + ev.Slug)
}
resultado
Envie para ver o que o servidor faria (envio simulado no navegador; o POST real está no código ao lado).

Salvou, viu.

trilha dev observa app/, regenera o registro de rotas, recompila, troca o processo e avisa o navegador. Um ciclo leva cerca de um segundo; mudanças em CSS chegam em milissegundos, sem recompilar. Erro de compilação vira uma página no navegador que some quando você corrige.

Zero dependências

Runtime e CLI usam só a biblioteca padrão. O roteador é o http.ServeMux do Go 1.22.

Sem mágica em runtime

A CLI gera um arquivo Go com o registro de rotas. O compilador confere cada assinatura. Sem reflect.

Um binário

trilha build embute public/ e produz um executável que sobe sozinho. trilha export gera um site estático, como este.

Comece pela primeira página

A trilha de aprendizado vai do trilha new até o deploy, construindo uma agenda de eventos. Cada capítulo termina com um desafio.

Aprender Trilha