O que é Template em Go?

Um template em Go é um mecanismo da biblioteca padrão para gerar saída textual a partir de dados estruturados. Go oferece dois pacotes de template: text/template para texto genérico e html/template para HTML com proteção automática contra XSS (Cross-Site Scripting). Ambos compartilham a mesma sintaxe e API, mas html/template adiciona escaping contextual que é essencial para aplicações web seguras.

O sistema de templates de Go usa uma abordagem baseada em ações (delimitadas por {{ }}) que são substituídas por valores durante a execução. Diferente de engines de template como Jinja2 (Python) ou ERB (Ruby), os templates de Go são deliberadamente simples — eles não permitem lógica de negócios complexa dentro do template, forçando uma separação limpa entre apresentação e lógica.

Essa filosofia se alinha com a abordagem geral de Go de simplicidade e clareza. Templates são usados em diversas situações: renderização de páginas web, geração de emails, criação de arquivos de configuração, geração de código e até na formatação de saída de ferramentas CLI. Para aplicações web modernas, consulte também o artigo sobre Go com HTMX e Templ para templates type-safe.

Template básico — text/template

O fluxo de trabalho com templates envolve três etapas: definir, parsear e executar.

package main

import (
    "os"
    "text/template"
)

func main() {
    // 1. Definir o template
    tmplStr := `Olá, {{.Nome}}!
Você tem {{.Idade}} anos e trabalha com {{.Linguagem}}.
{{if .Senior}}Nível: Sênior{{else}}Nível: Pleno{{end}}
`

    // 2. Parsear
    tmpl, err := template.New("saudacao").Parse(tmplStr)
    if err != nil {
        panic(err)
    }

    // 3. Executar com dados
    dados := struct {
        Nome      string
        Idade     int
        Linguagem string
        Senior    bool
    }{
        Nome:      "Ana",
        Idade:     28,
        Linguagem: "Go",
        Senior:    true,
    }

    tmpl.Execute(os.Stdout, dados)
}

Saída:

Olá, Ana!
Você tem 28 anos e trabalha com Go.
Nível: Sênior

O ponto (.) no template refere-se ao dado atual no escopo. No nível mais externo, . é o objeto passado para Execute. Dentro de loops e blocos, . muda para o item atual — similar ao conceito de receiver em métodos Go.

html/template — templates seguros para web

Para aplicações web, sempre use html/template em vez de text/template. O pacote HTML faz escaping automático contextual:

package main

import (
    "html/template"
    "net/http"
)

func main() {
    tmpl := template.Must(template.ParseFiles("templates/pagina.html"))

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        dados := struct {
            Titulo     string
            Conteudo   string
            Comentario string
        }{
            Titulo:     "Minha Página",
            Conteudo:   "Bem-vindo ao site!",
            Comentario: `<script>alert("XSS")</script>`, // tentativa de ataque
        }
        tmpl.Execute(w, dados)
    })

    http.ListenAndServe(":8080", nil)
}

Com o template HTML:

<!DOCTYPE html>
<html lang="pt-br">
<head><title>{{.Titulo}}</title></head>
<body>
    <h1>{{.Conteudo}}</h1>
    <p>Comentário: {{.Comentario}}</p>
</body>
</html>

O html/template automaticamente escapa {{.Comentario}} para &lt;script&gt;alert(&#34;XSS&#34;)&lt;/script&gt;, neutralizando o ataque XSS. Isso funciona em todos os contextos — HTML, atributos, JavaScript, CSS e URLs. Para mais sobre segurança em Go, consulte o tutorial de segurança.

Ações e controle de fluxo

Templates suportam condicionais, loops e controle de escopo:

const tmplStr = `
{{/* Comentário — não aparece na saída */}}

{{/* Condicional */}}
{{if .Premium}}
    <div class="premium">Conta Premium</div>
{{else if .Trial}}
    <div class="trial">Período de teste — {{.DiasRestantes}} dias</div>
{{else}}
    <div class="free">Conta gratuita</div>
{{end}}

{{/* Loop com range */}}
<ul>
{{range .Produtos}}
    <li>{{.Nome}} — R$ {{printf "%.2f" .Preco}}</li>
{{else}}
    <li>Nenhum produto encontrado</li>
{{end}}
</ul>

{{/* Range com índice */}}
{{range $i, $p := .Produtos}}
    <div>{{$i}}. {{$p.Nome}}</div>
{{end}}

{{/* Variáveis */}}
{{$total := len .Produtos}}
<p>Total: {{$total}} produtos</p>

{{/* With — muda o escopo do ponto */}}
{{with .Autor}}
    <p>Por {{.Nome}} ({{.Email}})</p>
{{end}}
`

O range itera sobre slices, arrays e maps. O bloco else dentro do range é executado quando a coleção está vazia. O with muda o escopo de . para o valor especificado — útil para acessar campos aninhados sem repetição.

Funções de template

Templates têm funções built-in e permitem funções customizadas:

// Funções built-in principais
const builtins = `
{{len .Items}}              {{/* comprimento */}}
{{index .Items 0}}          {{/* acesso por índice */}}
{{printf "%.2f" .Preco}}    {{/* formatação */}}
{{and .A .B}}               {{/* AND lógico */}}
{{or .A .B}}                {{/* OR lógico */}}
{{not .Ativo}}              {{/* negação */}}
{{eq .Status "ativo"}}      {{/* igualdade */}}
{{lt .Idade 18}}            {{/* menor que */}}
{{call .Funcao .Arg}}       {{/* chamada de função */}}
`

// Funções customizadas com FuncMap
func main() {
    funcMap := template.FuncMap{
        "maiusculo": strings.ToUpper,
        "monetario": func(valor float64) string {
            return fmt.Sprintf("R$ %.2f", valor)
        },
        "dataFormatada": func(t time.Time) string {
            return t.Format("02/01/2006")
        },
        "truncar": func(s string, max int) string {
            if len(s) <= max {
                return s
            }
            return s[:max] + "..."
        },
        "safeHTML": func(s string) template.HTML {
            return template.HTML(s) // CUIDADO: desativa escaping
        },
    }

    tmpl := template.Must(
        template.New("pagina").Funcs(funcMap).Parse(`
            <h1>{{maiusculo .Titulo}}</h1>
            <p>Preço: {{monetario .Preco}}</p>
            <p>Data: {{dataFormatada .Data}}</p>
            <p>Resumo: {{truncar .Descricao 100}}</p>
        `),
    )

    tmpl.Execute(os.Stdout, dados)
}

O FuncMap deve ser registrado antes do Parse. A função safeHTML merece atenção especial: ela desativa o escaping automático, portanto só deve ser usada com conteúdo que você controla completamente — nunca com input do usuário.

Pipelines

Pipelines permitem encadear operações com o operador |, similar ao pipe do Unix:

const tmplStr = `
{{/* Pipeline simples */}}
{{.Nome | maiusculo}}

{{/* Pipeline com múltiplos estágios */}}
{{.Descricao | truncar 50 | maiusculo}}

{{/* Pipeline com condicional */}}
{{if .Produtos | len | lt 0}}
    <p>Sem produtos</p>
{{end}}

{{/* Pipeline com printf */}}
{{"Olá, %s! Você tem %d itens." | printf .Nome .Total}}
`

Pipelines são a forma idiomática de compor transformações em templates, mantendo a legibilidade. O resultado de cada estágio é passado como último argumento do próximo.

Herança de templates — block/define/template

Templates podem ser compostos usando define, block e template:

// base.html — template base
const baseHTML = `
<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <title>{{block "titulo" .}}Site Padrão{{end}}</title>
    {{block "head_extra" .}}{{end}}
</head>
<body>
    <nav>{{template "nav" .}}</nav>
    <main>{{block "conteudo" .}}Conteúdo padrão{{end}}</main>
    <footer>{{template "footer" .}}</footer>
</body>
</html>
`

// nav.html
const navHTML = `
{{define "nav"}}
<ul>
    <li><a href="/">Home</a></li>
    <li><a href="/sobre">Sobre</a></li>
    <li><a href="/contato">Contato</a></li>
</ul>
{{end}}
`

// pagina.html — sobrescreve blocos da base
const paginaHTML = `
{{define "titulo"}}{{.Titulo}} | Meu Site{{end}}
{{define "conteudo"}}
<article>
    <h1>{{.Titulo}}</h1>
    <div>{{.Corpo}}</div>
</article>
{{end}}
`

func main() {
    tmpl := template.Must(
        template.New("base").Parse(baseHTML),
    )
    template.Must(tmpl.Parse(navHTML))
    template.Must(tmpl.Parse(paginaHTML))

    dados := struct {
        Titulo string
        Corpo  template.HTML
    }{
        Titulo: "Artigo sobre Go",
        Corpo:  "<p>Go é uma linguagem incrível para backend.</p>",
    }

    tmpl.ExecuteTemplate(os.Stdout, "base", dados)
}

A diferença entre block e template: block define um bloco com conteúdo padrão que pode ser sobrescrito, enquanto template inclui um template nomeado sem fallback. Esse sistema é a base de layouts em aplicações web Go, equivalente à herança de templates do Django ou Blade (Laravel).

Templates com arquivos

Em produção, templates ficam em arquivos separados:

func main() {
    // Parsear todos os templates de uma vez
    tmpl := template.Must(template.New("").Funcs(funcMap).ParseGlob("templates/*.html"))

    // Ou parsear arquivos específicos
    tmpl2 := template.Must(template.ParseFiles(
        "templates/base.html",
        "templates/nav.html",
        "templates/footer.html",
        "templates/home.html",
    ))

    mux := http.NewServeMux()
    mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "base.html", dados)
    })

    http.ListenAndServe(":8080", mux)
}

Embedding templates (Go 1.16+)

Com embed, templates são compilados junto com o binário:

//go:embed templates/*.html
var templateFS embed.FS

func main() {
    tmpl := template.Must(
        template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
    )

    // Agora o binário contém todos os templates — zero dependências de arquivo
    mux := http.NewServeMux()
    mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
        tmpl.ExecuteTemplate(w, "home.html", dadosHome())
    })

    http.ListenAndServe(":8080", mux)
}

Isso é ideal para deploy em containers Docker — um único binário sem diretório de templates externo.

Templates e handlers HTTP

O padrão completo para usar templates com handlers HTTP:

type App struct {
    templates *template.Template
    db        *sql.DB
}

func NewApp(db *sql.DB) *App {
    funcMap := template.FuncMap{
        "monetario":    formatarMoeda,
        "dataRelativa": dataRelativa,
    }

    tmpl := template.Must(
        template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
    )

    return &App{templates: tmpl, db: db}
}

func (app *App) renderizar(w http.ResponseWriter, tmplName string, dados interface{}) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    if err := app.templates.ExecuteTemplate(w, tmplName, dados); err != nil {
        slog.Error("erro ao renderizar template", "template", tmplName, "err", err)
        http.Error(w, "Erro interno", http.StatusInternalServerError)
    }
}

func (app *App) homeHandler(w http.ResponseWriter, r *http.Request) {
    produtos, err := app.listarProdutos(r.Context())
    if err != nil {
        http.Error(w, "Erro interno", http.StatusInternalServerError)
        return
    }
    app.renderizar(w, "home.html", map[string]interface{}{
        "Titulo":   "Bem-vindo",
        "Produtos": produtos,
    })
}

O método renderizar centraliza o tratamento de erros de template, evitando duplicação em cada handler. Para organização avançada, consulte o tutorial de Clean Architecture.

Segurança em templates HTML

O html/template faz escaping automático baseado no contexto:

// SEGURO — html/template faz escaping automático
tmpl := template.Must(template.New("").Parse(`
    <p>{{.Texto}}</p>              {{/* Escapa HTML */}}
    <a href="{{.URL}}">Link</a>    {{/* Escapa URL */}}
    <div style="{{.Estilo}}">      {{/* Escapa CSS */}}
    <script>var x = {{.JSON}}</script> {{/* Escapa JS */}}
`))

// Tipos especiais para conteúdo confiável
type DadosPagina struct {
    HTMLConfiavel template.HTML // NÃO será escapado
    URLConfiavel  template.URL  // NÃO será escapado
    CSSConfiavel  template.CSS  // NÃO será escapado
    JSConfiavel   template.JS   // NÃO será escapado
}

Use os tipos template.HTML, template.URL, template.CSS e template.JS apenas com conteúdo que você controla. Nunca converta input do usuário para esses tipos — isso desativaria a proteção contra XSS.

Alternativas modernas: Templ

Para projetos que precisam de type safety em templates, considere o Templ — uma engine que gera código Go a partir de templates, oferecendo verificação em tempo de compilação:

// componente.templ — compilado para Go
templ Pagina(titulo string, items []Item) {
    <!DOCTYPE html>
    <html lang="pt-br">
    <head><title>{titulo}</title></head>
    <body>
        <h1>{titulo}</h1>
        for _, item := range items {
            <div>{item.Nome}</div>
        }
    </body>
    </html>
}

Perguntas Frequentes

Qual a diferença entre text/template e html/template?

A sintaxe e API são idênticas. A diferença fundamental é que html/template adiciona escaping automático contextual: conteúdo inserido em HTML é escapado como HTML, em URLs como URL, em CSS como CSS e em JavaScript como JS. Isso previne ataques XSS automaticamente. Para qualquer aplicação web, sempre use html/template. Use text/template apenas para geração de texto não-HTML (configs, emails texto puro, código).

Como fazer herança de templates em Go?

Use {{block "nome" .}}conteúdo padrão{{end}} no template base para definir blocos que podem ser sobrescritos. Nos templates filhos, use {{define "nome"}}novo conteúdo{{end}} para substituir o bloco. Combine múltiplos templates com ParseFiles ou ParseGlob, e renderize com ExecuteTemplate. Esse padrão é equivalente à herança de templates do Django ou Blade.

Templates são lentos em Go?

Templates parseados são rápidos — o parsing acontece uma vez (no startup) e a execução usa o template compilado. Um benchmark típico mostra renderização de templates HTML complexos em microsegundos. A recomendação é parsear templates uma vez com template.Must e reutilizar. Nunca parsear dentro de handlers HTTP — isso criaria overhead desnecessário por requisição.

Quando usar Templ em vez de html/template?

Use html/template para projetos simples a médios onde a flexibilidade de templates dinâmicos é vantajosa. Use Templ quando precisar de: verificação de tipos em tempo de compilação, autocompletar na IDE, componentes reutilizáveis com parâmetros tipados e detecção de erros de template antes do deploy. Templ é especialmente útil em projetos grandes com equipes que preferem segurança de tipos.