<h2>Fundamentos de APIs REST em Go Puro</h2>
<p>Go é uma linguagem extremamente eficiente para construir APIs REST de alta performance. Diferentemente de linguagens que dependem fortemente de frameworks, Go oferece uma biblioteca padrão robusta (<code>net/http</code>) que fornece tudo o que você precisa para criar endpoints profissionais. Muitos desenvolvedores assumem erroneamente que precisam de um framework como Gin ou Echo para fazer isso, mas a realidade é que o pacote <code>net/http</code> é suficientemente poderoso e maduro para aplicações em produção.</p>
<p>O principal conceito que você precisa compreender é como o <code>net/http</code> trabalha. Quando você inicia um servidor HTTP em Go, você está basicamente registrando <strong>handlers</strong> — funções que recebem uma solicitação HTTP e escrevem uma resposta. Esses handlers seguem a interface <code>http.Handler</code>, que exige apenas um método: <code>ServeHTTP(ResponseWriter, *Request)</code>. Essa simplicidade é a força de Go: você não está preso a convenções complexas ou configurações mágicas.</p>
<p>Vamos começar com o exemplo mais fundamental — um servidor HTTP básico:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"net/http"
)
func main() {
// Registrar um handler simples
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"mensagem": "Olá, mundo!",
})
})
// Iniciar servidor na porta 8080
http.ListenAndServe(":8080", nil)
}</code></pre>
<p>Quando você executa este código e acessa <code>http://localhost:8080</code>, o handler é invocado. O <code>ResponseWriter</code> permite que você escreva headers e o corpo da resposta, enquanto <code>*Request</code> contém todas as informações sobre a requisição recebida. Simples, mas poderoso.</p>
<h2>Roteamento Avançado Sem Framework</h2>
<p>O roteamento é a capacidade de mapear diferentes caminhos de URL para diferentes handlers. A abordagem básica usando <code>http.HandleFunc()</code> funciona bem para aplicações pequenas, mas fica limitada quando você precisa de rotas parametrizadas, métodos HTTP específicos ou padrões mais complexos. Aqui está onde a coisa fica interessante: você pode construir um roteador sofisticado usando apenas Go puro.</p>
<h3>Por Que Não Usar o Roteamento Padrão?</h3>
<p>O <code>http.HandleFunc()</code> registra rotas de forma estática e não oferece suporte nativo para parâmetros dinâmicos. Se você tentar acessar <code>/usuarios/123</code>, o <code>http.HandleFunc()</code> não consegue extrair o <code>123</code> automaticamente. Além disso, ele trata todas as requisições da mesma forma independentemente do método HTTP (GET, POST, etc.).</p>
<p>Vamos construir um roteador personalizado que resolve esses problemas:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"net/http"
"strings"
)
// Handler é um tipo de função que define como tratamos requisições
type Handler func(http.ResponseWriter, *http.Request, map[string]string)
// Rota define um padrão de URL, método HTTP e handler
type Rota struct {
Metodo string
Padrao string
Handler Handler
}
// Roteador armazena todas as rotas registradas
type Roteador struct {
rotas []*Rota
}
// Registrar adiciona uma rota ao roteador
func (r *Roteador) Registrar(metodo, padrao string, handler Handler) {
r.rotas = append(r.rotas, &Rota{
Metodo: metodo,
Padrao: padrao,
Handler: handler,
})
}
// extrairParametros compara um padrão com um caminho e extrai parâmetros
func extrairParametros(padrao, caminho string) (map[string]string, bool) {
partes_padrao := strings.Split(strings.Trim(padrao, "/"), "/")
partes_caminho := strings.Split(strings.Trim(caminho, "/"), "/")
if len(partes_padrao) != len(partes_caminho) {
return nil, false
}
params := make(map[string]string)
for i, parte := range partes_padrao {
// Se a parte começa com :, é um parâmetro
if strings.HasPrefix(parte, ":") {
nome := strings.TrimPrefix(parte, ":")
params[nome] = partes_caminho[i]
} else if parte != partes_caminho[i] {
// Se não é parâmetro e não corresponde, falha
return nil, false
}
}
return params, true
}
// ServeHTTP implementa a interface http.Handler
func (r Roteador) ServeHTTP(w http.ResponseWriter, req http.Request) {
for _, rota := range r.rotas {
// Verificar se o método HTTP corresponde
if rota.Metodo != req.Method {
continue
}
// Tentar extrair parâmetros
params, encontrado := extrairParametros(rota.Padrao, req.URL.Path)
if encontrado {
rota.Handler(w, req, params)
return
}
}
// Se nenhuma rota encontrada, retornar 404
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{
"erro": "Rota não encontrada",
})
}
// Exemplo de handler com parâmetros
func obterUsuario(w http.ResponseWriter, r *http.Request, params map[string]string) {
usuarioID := params["id"]
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"id": usuarioID,
"nome": "João Silva",
})
}
func listarUsuarios(w http.ResponseWriter, r *http.Request, params map[string]string) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode([]map[string]string{
{"id": "1", "nome": "João"},
{"id": "2", "nome": "Maria"},
})
}
func main() {
roteador := &Roteador{}
// Registrar rotas
roteador.Registrar("GET", "/usuarios", listarUsuarios)
roteador.Registrar("GET", "/usuarios/:id", obterUsuario)
http.ListenAndServe(":8080", roteador)
}</code></pre>
<p>Neste exemplo, criamos um roteador que:</p>
<ol>
<li><strong>Armazena rotas</strong> com seu padrão, método HTTP e handler associado</li>
<li><strong>Extrai parâmetros</strong> analisando a URL — quando encontra <code>:id</code>, sabe que é um parâmetro dinâmico</li>
<li><strong>Implementa <code>http.Handler</code></strong> permitindo que seja usado diretamente com <code>http.ListenAndServe()</code></li>
<li><strong>Retorna 404</strong> se nenhuma rota corresponder</li>
</ol>
<p>Teste com <code>curl http://localhost:8080/usuarios/123</code> e você verá que o <code>123</code> é capturado como parâmetro.</p>
<h2>Middlewares: Tratamento Transversal de Requisições</h2>
<p>Middlewares são funções que "embrulham" seus handlers, permitindo executar lógica antes e depois da requisição ser processada. Pense em middlewares como um sistema de camadas: a requisição passa por todas as camadas antes de chegar ao handler real, e a resposta passa pelas mesmas camadas novamente.</p>
<h3>Entendendo a Cadeia de Middlewares</h3>
<p>Um middleware em Go é simplesmente uma função que recebe um <code>http.Handler</code> e retorna um novo <code>http.Handler</code>. Isso permite composição — você pode agrupar múltiplos middlewares e aplicar a qualquer rota. Os casos de uso mais comuns são: logging de requisições, autenticação, validação CORS, tratamento de erros e medição de performance.</p>
<p>Vamos construir um sistema robusto de middlewares:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
// Middleware é uma função que recebe um handler e retorna um novo handler
type Middleware func(http.Handler) http.Handler
// middlewareLogging registra detalhes de cada requisição
func middlewareLogging(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
// Executar o handler original
handler.ServeHTTP(w, r)
// Registrar informações após a requisição
duracao := time.Since(inicio)
log.Printf("[%s] %s %s - %v", r.Method, r.URL.Path, r.RemoteAddr, duracao)
})
}
// middlewareAutenticacao verifica um token simples no header
func middlewareAutenticacao(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
// Token inválido ou ausente
if token != "Bearer seu-token-secreto" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
"erro": "Token inválido ou ausente",
})
return
}
// Token válido, continuar
handler.ServeHTTP(w, r)
})
}
// middlewareCORS adiciona headers CORS
func middlewareCORS(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Se for um OPTIONS request, responder e encerrar
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
handler.ServeHTTP(w, r)
})
}
// middlewarePanico recupera de panics na aplicação
func middlewarePanico(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("PANIC: %v", err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"erro": "Erro interno do servidor",
})
}
}()
handler.ServeHTTP(w, r)
})
}
// aplicarMiddlewares aplica uma sequência de middlewares a um handler
func aplicarMiddlewares(handler http.Handler, middlewares ...Middleware) http.Handler {
// Aplicar middlewares na ordem reversa para que o primeiro middleware
// listado seja o primeiro a executar
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// Handler que simula uma operação
func handlerDados(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"dados": "Informação sensível",
"timestamp": time.Now(),
"autorizado": true,
})
}
func main() {
// Criar um handler base
handler := http.HandlerFunc(handlerDados)
// Aplicar middlewares — a ordem importa!
// Ordem de execução será: CORS → Autenticação → Logging → Panico → Handler
handler = aplicarMiddlewares(
handler,
middlewarePanico, // Executar por último (mas aplicado primeiro)
middlewareLogging,
middlewareAutenticacao,
middlewareCORS, // Executar por primeiro
)
http.Handle("/dados", handler)
fmt.Println("Servidor iniciado em :8080")
http.ListenAndServe(":8080", nil)
}</code></pre>
<p><strong>Por que a ordem é reversa?</strong> Quando aplicamos middlewares, cada um "envolve" o anterior. Se você pensa visualmente:</p>
<pre><code>Requisição → CORS → Autenticação → Logging → Panico → Handler</code></pre>
<p>Mas no código, você aplica na ordem inversa para conseguir esse resultado. Se você tentar acessar <code>curl -H "Authorization: Bearer seu-token-secreto" http://localhost:8080/dados</code>, verá o middleware de CORS permitindo, a autenticação validando o token, o logging registrando a requisição, e o handler finalmente executando.</p>
<h3>Contexto de Requisição com Middlewares</h3>
<p>Um padrão avançado é usar o contexto (<code>context.Context</code>) para passar dados entre middlewares e handlers. Isso é especialmente útil quando middlewares precisam compartilhar informações:</p>
<pre><code class="language-go">package main
import (
"context"
"encoding/json"
"net/http"
)
// HandlerComContexto é um tipo de handler que aceita contexto customizado
type HandlerComContexto func(http.ResponseWriter, *http.Request)
// middlewareUsuario extrai o usuário do token e o coloca no contexto
func middlewareUsuario(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Simular extração de usuário do token
usuarioID := "usuario-123"
nomeUsuario := "João"
// Criar novo contexto com os dados do usuário
ctx := context.WithValue(r.Context(), "usuarioID", usuarioID)
ctx = context.WithValue(ctx, "nomeUsuario", nomeUsuario)
// Passar a requisição com o novo contexto
handler.ServeHTTP(w, r.WithContext(ctx))
})
}
// Handler que usa dados do contexto
func handlerPerfil(w http.ResponseWriter, r *http.Request) {
usuarioID := r.Context().Value("usuarioID").(string)
nomeUsuario := r.Context().Value("nomeUsuario").(string)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"id": usuarioID,
"nome": nomeUsuario,
})
}
func main() {
handler := http.HandlerFunc(handlerPerfil)
handler = middlewareUsuario(handler)
http.Handle("/perfil", handler)
http.ListenAndServe(":8080", nil)
}</code></pre>
<h2>Integração Completa: API REST Funcional</h2>
<p>Até agora vimos componentes isolados. Vamos agora montar uma API REST completa que integra roteamento avançado com middlewares robusos:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
)
// Tipos para nossa API
type Usuario struct {
ID string json:"id"
Nome string json:"nome"
Email string json:"email"
}
type Rota struct {
Metodo string
Padrao string
Handler http.Handler
}
type Roteador struct {
rotas []*Rota
}
// Banco de dados simulado
var usuarios = map[string]Usuario{
"1": {ID: "1", Nome: "João Silva", Email: "joao@example.com"},
"2": {ID: "2", Nome: "Maria Santos", Email: "maria@example.com"},
}
// Registrar adiciona uma rota com middlewares aplicados
func (r *Roteador) Registrar(metodo, padrao string, handler http.Handler) {
r.rotas = append(r.rotas, &Rota{
Metodo: metodo,
Padrao: padrao,
Handler: handler,
})
}
// extrairParametros extrai parâmetros da URL
func extrairParametros(padrao, caminho string) (map[string]string, bool) {
partes_padrao := strings.Split(strings.Trim(padrao, "/"), "/")
partes_caminho := strings.Split(strings.Trim(caminho, "/"), "/")
if len(partes_padrao) != len(partes_caminho) {
return nil, false
}
params := make(map[string]string)
for i, parte := range partes_padrao {
if strings.HasPrefix(parte, ":") {
nome := strings.TrimPrefix(parte, ":")
params[nome] = partes_caminho[i]
} else if parte != partes_caminho[i] {
return nil, false
}
}
return params, true
}
// ServeHTTP implementa http.Handler
func (r Roteador) ServeHTTP(w http.ResponseWriter, req http.Request) {
for _, rota := range r.rotas {
if rota.Metodo != req.Method {
continue
}
params, encontrado := extrairParametros(rota.Padrao, req.URL.Path)
if encontrado {
// Passar parâmetros via contexto
ctx := req.Context()
for chave, valor := range params {
ctx = context.WithValue(ctx, "param_"+chave, valor)
}
rota.Handler.ServeHTTP(w, req.WithContext(ctx))
return
}
}
// 404
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"erro": "Não encontrado"})
}
// Middlewares
func middlewareLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
next.ServeHTTP(w, r)
log.Printf("[%s] %s - %v", r.Method, r.URL.Path, time.Since(inicio))
})
}
func middlewareContentType(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func middlewareAutenticacao(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Rotas públicas não precisam de autenticação
if r.URL.Path == "/usuarios" && r.Method == "GET" {
next.ServeHTTP(w, r)
return
}
token := r.Header.Get("Authorization")
if token != "Bearer token-secreto" {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{"erro": "Não autorizado"})
return
}
next.ServeHTTP(w, r)
})
}
func aplicarMiddlewares(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// Handlers
func listarUsuarios(w http.ResponseWriter, r *http.Request) {
usuariosList := make([]Usuario, 0, len(usuarios))
for _, u := range usuarios {
usuariosList = append(usuariosList, u)
}
json.NewEncoder(w).Encode(usuariosList)
}
func obterUsuario(w http.ResponseWriter, r *http.Request) {
id := r.Context().Value("param_id").(string)
usuario, existe := usuarios[id]
if !existe {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{"erro": "Usuário não encontrado"})
return
}
json.NewEncoder(w).Encode(usuario)
}
func criarUsuario(w http.ResponseWriter, r *http.Request) {
var usuario Usuario
if err := json.NewDecoder(r.Body).Decode(&usuario); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{"erro": "JSON inválido"})
return
}
usuarios[usuario.ID] = usuario
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(usuario)
}
func main() {
roteador := &Roteador{}
// Aplicar middlewares para todas as rotas
baseHandler := aplicarMiddlewares(
http.Handler(roteador),
middlewareLogging,
middlewareContentType,
middlewareAutenticacao,
)
// Registrar rotas
roteador.Registrar("GET", "/usuarios", http.HandlerFunc(listarUsuarios))
roteador.Registrar("GET", "/usuarios/:id", http.HandlerFunc(obterUsuario))
roteador.Registrar("POST", "/usuarios", http.HandlerFunc(criarUsuario))
http.Handle("/", baseHandler)
fmt.Println("API iniciada em :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}</code></pre>
<p>Note que falta <code>import "context"</code> — adicione no início do arquivo. Este exemplo completo demonstra:</p>
<ul>
<li><strong>Roteamento com parâmetros</strong> — <code>/usuarios/:id</code> extrai o ID da URL</li>
<li><strong>Passagem de contexto</strong> — Parâmetros são passados via <code>context.Context</code></li>
<li><strong>Middlewares globais</strong> — Todos os handlers recebem logging e validação de content-type</li>
<li><strong>Autenticação condicional</strong> — Apenas rotas POST/PUT/DELETE exigem token</li>
<li><strong>Tratamento de erros</strong> — Status HTTP apropriados para cada cenário</li>
</ul>
<p>Teste com:</p>
<pre><code class="language-bash"># Listar usuários (sem autenticação)
curl http://localhost:8080/usuarios
Obter usuário específico
curl http://localhost:8080/usuarios/1
Criar usuário (requer token)
curl -X POST -H "Authorization: Bearer token-secreto" \
-H "Content-Type: application/json" \
-d '{"id":"3","nome":"Pedro","email":"pedro@example.com"}' \
http://localhost:8080/usuarios</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>Go puro oferece poder suficiente para construir APIs REST profissionais</strong> sem precisar de frameworks externos. O pacote <code>net/http</code> combinado com padrões bem estruturados (implementar <code>http.Handler</code>, usar middlewares como wrappers de funções, aplicar contexto para compartilhar dados) resulta em código limpo, performático e fácil de testar.</p>
<p>O segundo ponto crucial é que <strong>middlewares em Go não são mágicos</strong> — são simplesmente funções que recebem handlers e retornam handlers, permitindo composição elegante. Essa simplicidade torna fácil adicionar logging, autenticação, tratamento de erros e qualquer outra lógica transversal sem poluir seus handlers.</p>
<p>Por fim, você viu que <strong>roteamento avançado é construível com menos de 50 linhas de código</strong> — extraindo parâmetros de URLs e mapeando métodos HTTP. Isso demonstra uma filosofia fundamental de Go: ser explícito e fazer muito com pouco, evitando abstrações desnecessárias que ocultam o que realmente está acontecendo.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://golang.org/pkg/net/http/" target="_blank" rel="noopener noreferrer">Net/HTTP Package - Documentação Oficial Go</a></li>
<li><a href="https://golang.org/pkg/context/" target="_blank" rel="noopener noreferrer">Context Package - Go Documentation</a></li>
<li><a href="https://golang.org/doc/articles/wiki/" target="_blank" rel="noopener noreferrer">Writing Web Applications - Go Tutorial</a></li>
<li><a href="https://github.com/golang-standards/project-layout" target="_blank" rel="noopener noreferrer">Building REST APIs in Go</a></li>
<li><a href="https://blog.golang.org/using-go-modules" target="_blank" rel="noopener noreferrer">The Go Blog: Using Go Modules</a></li>
</ul>
<p><!-- FIM --></p>