Bem-vindo à aula 59 do nosso curso de Go! Hoje vamos explorar o pacote html/template, uma das ferramentas mais poderosas e seguras para gerar HTML dinâmico no servidor. Diferente da concatenação manual de strings, o pacote html/template não só permite injetar dados em páginas HTML, mas também protege automaticamente contra ataques de injeção de código (como XSS). Isso é essencial para qualquer aplicação web que lide com dados do usuário.

Nesta aula, vamos cobrir desde o básico de parsing e execução até técnicas avançadas de composição de templates, passando pelo sistema de variáveis e pelo escape automático. Ao final, você terá uma base sólida para criar páginas web dinâmicas e seguras em Go.

Parsing e execução

O primeiro passo para usar templates é fazer o parsing (análise) do texto do template. O pacote html/template oferece a função template.New para criar um novo template e Parse (ou ParseFiles, ParseGlob) para analisar o conteúdo. O parsing valida a sintaxe do template e prepara a estrutura interna para execução.

Depois de parseado, o template pode ser executado com o método Execute, que recebe um io.Writer (como um http.ResponseWriter ou um buffer) e os dados que serão injetados. A execução substitui as ações (como {{.}}) pelos valores fornecidos. É importante notar que a execução pode falhar se houver erros de tipo ou se o template tentar acessar campos inexistentes.

Vamos ver um exemplo simples:

package main

import (
    "html/template"
    "os"
)

func main() {
    // Cria um template com o nome "exemplo"
    t := template.New("exemplo")

    // Faz o parsing de uma string simples
    t, err := t.Parse("Olá, {{.}}!")
    if err != nil {
        panic(err)
    }

    // Executa o template, escrevendo na saída padrão
    err = t.Execute(os.Stdout, "Mundo")
    if err != nil {
        panic(err)
    }
}

Nesse exemplo, o template contém a ação {{.}}, que representa o valor passado como dados (a string "Mundo"). A saída será Olá, Mundo!.

Além de strings, você pode fazer parsing de arquivos usando template.ParseFiles, que recebe um ou mais nomes de arquivo e cria um template com o nome do primeiro arquivo. Para múltiplos arquivos, é comum usar ParseGlob para carregar todos os arquivos que correspondem a um padrão.

Variáveis

No sistema de templates do Go, as variáveis são definidas usando a ação {{$var := valor}} dentro do próprio template. Elas permitem armazenar valores intermediários e reutilizá-los, tornando os templates mais expressivos. As variáveis são especialmente úteis em loops e condicionais.

Por exemplo, você pode capturar o resultado de uma operação ou o valor de um campo em uma variável para usar depois:

package main

import (
    "html/template"
    "os"
)

func main() {
    const tpl = `
{{- $nome := "João" -}}
{{- $idade := 30 -}}
Nome: {{$nome}}, Idade: {{$idade}}
`
    t := template.Must(template.New("vars").Parse(tpl))
    t.Execute(os.Stdout, nil)
}

Aqui, definimos duas variáveis $nome e $idade e depois as usamos. O template.Must é uma função auxiliar que entra em pânico se o parsing falhar, útil para templates definidos em tempo de compilação.

As variáveis também podem ser usadas em estruturas de controle, como if e range. Por exemplo, dentro de um loop, você pode capturar o índice ou o valor:

{{range $i, $v := .Itens}}
    Índice: {{$i}}, Valor: {{$v}}
{{end}}

É importante notar que o escopo de uma variável é o bloco onde ela é definida. Variáveis definidas fora de um bloco range não são acessíveis dentro dele, a menos que sejam passadas explicitamente.

Escape automático

Uma das maiores vantagens do pacote html/template é o escape automático de dados. Quando você injeta um valor em um template, o pacote analisa o contexto (por exemplo, se está dentro de uma tag HTML, atributo, CSS, JavaScript ou URL) e aplica o escape apropriado. Isso previne ataques de injeção de código, como XSS, sem que o desenvolvedor precise se preocupar manualmente com cada caso.

Por exemplo, se você passar a string <script>alert('XSS')</script> como dado, o template a exibirá como texto puro, escapando os caracteres especiais:

package main

import (
    "html/template"
    "os"
)

func main() {
    t := template.Must(template.New("escape").Parse("{{.}}"))
    t.Execute(os.Stdout, "<script>alert('XSS')</script>")
}

A saída será &lt;script&gt;alert('XSS')&lt;/script&gt;, que no navegador aparece como texto, não como código executável. O escape é feito automaticamente com base no contexto: em atributos HTML, por exemplo, o valor é escapado de forma diferente.

Para desabilitar o escape em casos específicos (quando você confia no conteúdo), o pacote oferece o tipo template.HTML. Ao usar esse tipo, o valor é injetado sem escape. Isso deve ser usado com extrema cautela, apenas para conteúdo 100% confiável, pois abre brechas para XSS.

Composição

Em aplicações reais, é comum ter templates que se repetem (como cabeçalhos, rodapés e barras laterais). O pacote html/template permite compor templates de várias formas: definindo blocos, usando template dentro de template e usando define para nomear partes reutilizáveis.

Uma abordagem comum é usar define para criar templates parciais e depois invocá-los com template. Por exemplo, você pode definir um template para o cabeçalho e outro para o rodapé:

package main

import (
    "html/template"
    "os"
)

func main() {
    const tpl = `
{{define "header"}}
Meu Site
{{end}} {{define "footer"}}
© 2025
{{end}} Exemplo {{template "header" .}}

Conteúdo principal.

{{template "footer" .}} ` t := template.Must(template.New("page").Parse(tpl)) t.Execute(os.Stdout, nil) }

Nesse exemplo, definimos dois templates parciais (header e footer) e os invocamos dentro do template principal. Note que ao invocar um template com {{template "nome" .}}, você pode passar dados (aqui, . representa os dados atuais, que podem ser nil).

Outra técnica é usar block para definir um template padrão que pode ser sobrescrito por templates que o incluem. Isso é útil para layouts. Por exemplo, você pode ter um layout base com um bloco de conteúdo e depois criar páginas que preenchem esse bloco.

// layout.html
{{define "layout"}}


{{block "title" .}}Título Padrão{{end}}

{{block "content" .}}Conteúdo padrão{{end}}


{{end}}

// page.html
{{define "content"}}

Página específica

{{end}}

Para usar isso, você precisaria parsear ambos os arquivos e executar o template layout com os dados. O bloco content será substituído pela definição da página.

Essa composição permite criar layouts reutilizáveis e manter o código organizado.

Boas práticas e observações finais

Ao trabalhar com templates em Go, algumas boas práticas são essenciais:

  • Use template.Must para templates que não podem falhar: Isso simplifica o código e evita tratamento de erro repetitivo.
  • Evite injetar HTML não confiável: Sempre use o escape automático; apenas use template.HTML se você tiver certeza absoluta do conteúdo.
  • Organize seus templates em arquivos: Para projetos maiores, use ParseFiles ou ParseGlob para carregar templates de forma modular.
  • Componha templates com define e template: Isso reduz duplicação e facilita a manutenção.
  • Teste seus templates: Use go test para verificar se os templates são válidos e produzem a saída esperada.

O pacote html/template é uma ferramenta robusta e segura. Dominá-lo é fundamental para o desenvolvimento web em Go.

Referências

Exercícios

  1. Exercício 1: Crie um template que receba uma struct com os campos Nome e Idade e os exiba em uma frase. Execute o template com um exemplo.
  2. ✓ Resposta:
    package main
    
    import (
        "html/template"
        "os"
    )
    
    type Pessoa struct {
        Nome  string
        Idade int
    }
    
    func main() {
        const tpl = `Olá, {{.Nome}}! Você tem {{.Idade}} anos.`
        t := template.Must(template.New("ex1").Parse(tpl))
        p := Pessoa{"Maria", 25}
        t.Execute(os.Stdout, p)
    }
    
  3. Exercício 2: Escreva um template que use uma variável $saudacao definida com o valor "Bem-vindo" e a imprima junto com um nome passado nos dados.
  4. ✓ Resposta:
    package main
    
    import (
        "html/template"
        "os"
    )
    
    func main() {
        const tpl = `{{$saudacao := "Bem-vindo"}}{{$saudacao}}, {{.}}!`
        t := template.Must(template.New("ex2").Parse(tpl))
        t.Execute(os.Stdout, "Carlos")
    }
    
  5. Exercício 3: Crie um template que exiba uma lista de nomes (fatia de strings) usando range. Se a lista estiver vazia, exiba "Nenhum nome".
  6. ✓ Resposta:
    package main
    
    import (
        "html/template"
        "os"
    )
    
    func main() {
        const tpl = `{{if .}}
      {{range .}}
    • {{.}}
    • {{end}}
    {{else}}Nenhum nome{{end}}` t := template.Must(template.New("ex3").Parse(tpl)) nomes := []string{"Ana", "Bruno", "Carla"} t.Execute(os.Stdout, nomes) }
  7. Exercício 4: Explique o que acontece quando você passa a string <b>negrito</b> para um template que faz {{.}} e o que você faria para exibir como HTML real.
  8. ✓ Resposta: Por padrão, o template escapa a string, exibindo &lt;b&gt;negrito&lt;/b&gt; como texto. Para exibir como HTML real, você deve usar o tipo template.HTML para envolver a string: template.HTML("<b>negrito</b>"). Isso desabilita o escape.
  9. Exercício 5: Crie dois templates parciais (um para cabeçalho e um para rodapé) e um template principal que os inclua. Execute o template principal.
  10. ✓ Resposta:
    package main
    
    import (
        "html/template"
        "os"
    )
    
    func main() {
        const tpl = `
    {{define "header"}}
    Meu Site
    {{end}} {{define "footer"}}
    © 2025
    {{end}} {{template "header" .}}

    Conteúdo

    {{template "footer" .}} ` t := template.Must(template.New("page").Parse(tpl)) t.Execute(os.Stdout, nil) }