trilha

package module
v0.1.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: 22 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

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

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()
}

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, streaming, geração estática, 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: abra uma issue descrevendo a convenção ou o problema antes do PR, e siga o fluxo spec-kit em specs/ para mudanças de comportamento.

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) 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.

Variables

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).

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 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.

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) Env

func (a *App) Env() Env

Env returns the runtime environment.

func (*App) Handler

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

Handler returns the root http.Handler (useful for tests and embedding).

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) 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) 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
}

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) 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) 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) 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) 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) SetCookie

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

SetCookie adds a Set-Cookie header.

func (*Ctx) SetTitle

func (c *Ctx) SetTitle(t string)

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

func (*Ctx) Status

func (c *Ctx) Status(code int)

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

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 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).

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 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
}

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

Directories

Path Synopsis
cmd
trilha command
Command trilha is the CLI: new, gen, dev, build, routes.
Command trilha is the CLI: new, gen, dev, build, routes.
examples
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.
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.
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.

Jump to

Keyboard shortcuts

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