Templates HTML (html/template)
Nesta aula, você aprenderá a usar o pacote html/template do Go para gerar HTML dinâmico com segurança, incluindo parsing, execução, variáveis, escape automático e composição de templates.
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á <script>alert('XSS')</script>, 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"}}{{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.Mustpara 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.HTMLse você tiver certeza absoluta do conteúdo. - Organize seus templates em arquivos: Para projetos maiores, use
ParseFilesouParseGlobpara carregar templates de forma modular. - Componha templates com
defineetemplate: Isso reduz duplicação e facilita a manutenção. - Teste seus templates: Use
go testpara 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
- Documentação oficial do html/template
- Tutorial de web app em Go (inclui templates)
- Post do blog oficial: Templates
- How To Use Templates in Go (DigitalOcean)
- Go Web Examples: Templates
- Go HTML Templates - Curious Cat
Exercícios
- Exercício 1: Crie um template que receba uma struct com os campos
NomeeIdadee os exiba em uma frase. Execute o template com um exemplo. - Exercício 2: Escreva um template que use uma variável
$saudacaodefinida com o valor "Bem-vindo" e a imprima junto com um nome passado nos dados. - 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". - {{.}} {{end}}
- 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. - 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.
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)
}
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")
}
package main
import (
"html/template"
"os"
)
func main() {
const tpl = `{{if .}}{{range .}}
{{else}}Nenhum nome{{end}}`
t := template.Must(template.New("ex3").Parse(tpl))
nomes := []string{"Ana", "Bruno", "Carla"}
t.Execute(os.Stdout, nomes)
}
<b>negrito</b> 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.package main
import (
"html/template"
"os"
)
func main() {
const tpl = `
{{define "header"}}Meu Site {{end}}
{{define "footer"}}{{end}}
{{template "header" .}}
Conteúdo
{{template "footer" .}}
`
t := template.Must(template.New("page").Parse(tpl))
t.Execute(os.Stdout, nil)
}