O que é Middleware em Go?
Middleware em Go é uma função que envolve um handler HTTP para executar lógica antes e/ou depois do processamento da requisição. O padrão é elegantemente simples: uma função que recebe um http.Handler e retorna um novo http.Handler, criando uma cadeia de processamento onde cada camada pode inspecionar, modificar ou interromper o fluxo da requisição.
Esse padrão — tecnicamente chamado de “decorator” — é possível graças à interface http.Handler de Go e ao poder das funções como valores de primeira classe. Diferente de frameworks que usam pipelines complexos com reflexão ou injeção de dependência, middlewares em Go são funções puras que se compõem naturalmente. Isso os torna fáceis de escrever, testar e raciocinar sobre — e é a razão pela qual até frameworks populares como Chi e Echo usam exatamente esse padrão.
Na prática, middlewares são usados para cross-cutting concerns: logging, autenticação, CORS, rate limiting, compressão, métricas, tracing e recovery de panics. Em vez de repetir essa lógica em cada handler, você a escreve uma vez como middleware e aplica onde necessário.
O padrão fundamental
A assinatura de um middleware em Go é sempre a mesma:
func meuMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Lógica ANTES do handler
fmt.Println("Antes da requisição")
// Chama o próximo handler na cadeia
next.ServeHTTP(w, r)
// Lógica DEPOIS do handler
fmt.Println("Depois da requisição")
})
}
O next é o handler que será executado depois da lógica do middleware. Se você não chamar next.ServeHTTP(w, r), a cadeia é interrompida — útil para middlewares de autenticação que rejeitam requisições não autorizadas.
Middleware de logging
Logging é o middleware mais comum e o primeiro que todo servidor HTTP deveria ter:
type responseRecorder struct {
http.ResponseWriter
statusCode int
bytesWritten int
}
func (rr *responseRecorder) WriteHeader(code int) {
rr.statusCode = code
rr.ResponseWriter.WriteHeader(code)
}
func (rr *responseRecorder) Write(b []byte) (int, error) {
n, err := rr.ResponseWriter.Write(b)
rr.bytesWritten += n
return n, err
}
func comLogging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
rec := &responseRecorder{
ResponseWriter: w,
statusCode: http.StatusOK,
}
next.ServeHTTP(rec, r)
slog.Info("requisição HTTP",
"metodo", r.Method,
"path", r.URL.Path,
"status", rec.statusCode,
"duracao_ms", time.Since(inicio).Milliseconds(),
"bytes", rec.bytesWritten,
"ip", r.RemoteAddr,
"user_agent", r.UserAgent(),
)
})
}
O responseRecorder captura o status code e bytes escritos, pois o http.ResponseWriter padrão não expõe essas informações depois de enviar a resposta. Para logging estruturado em produção, consulte o guia de slog em Go e o pacote log.
Middleware de autenticação
Autenticação é o segundo middleware mais importante — ele protege rotas que exigem credenciais:
type contextKey string
const usuarioKey contextKey = "usuario"
func comAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, `{"erro":"token ausente"}`, http.StatusUnauthorized)
return // Interrompe a cadeia — next NÃO é chamado
}
// Remove prefixo "Bearer "
if len(token) > 7 && token[:7] == "Bearer " {
token = token[7:]
}
usuario, err := validarToken(token)
if err != nil {
http.Error(w, `{"erro":"token inválido"}`, http.StatusUnauthorized)
return
}
// Injeta o usuário no context para o handler usar
ctx := context.WithValue(r.Context(), usuarioKey, usuario)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// No handler, recupera o usuário do context
func perfilHandler(w http.ResponseWriter, r *http.Request) {
usuario, ok := r.Context().Value(usuarioKey).(Usuario)
if !ok {
http.Error(w, "Não autenticado", http.StatusUnauthorized)
return
}
json.NewEncoder(w).Encode(usuario)
}
Note o uso de context para passar dados entre middleware e handler, com uma chave tipada para evitar colisões. Para implementação completa de JWT, consulte o guia de autenticação JWT em Go.
Middleware de CORS
CORS (Cross-Origin Resource Sharing) é essencial para APIs consumidas por frontends em domínios diferentes:
func comCORS(origemPermitida string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", origemPermitida)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Max-Age", "86400")
// Preflight request
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
}
// Uso com parâmetro
mux := http.NewServeMux()
handler := comCORS("https://meusite.com.br")(mux)
Essa versão parametrizada demonstra como middlewares em Go podem aceitar configuração — retornando uma closure que fecha sobre os parâmetros. Esse padrão é comum em funções de ordem superior em Go.
Middleware de rate limiting
Rate limiting protege sua API contra abuso e excesso de requisições:
func comRateLimit(reqPorSegundo int) func(http.Handler) http.Handler {
limiter := rate.NewLimiter(rate.Limit(reqPorSegundo), reqPorSegundo*2)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
w.Header().Set("Retry-After", "1")
http.Error(w, `{"erro":"muitas requisições"}`, http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// Rate limit por IP
func comRateLimitPorIP(reqPorSegundo int) func(http.Handler) http.Handler {
var mu sync.Mutex
limiters := make(map[string]*rate.Limiter)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
mu.Lock()
lim, existe := limiters[ip]
if !existe {
lim = rate.NewLimiter(rate.Limit(reqPorSegundo), reqPorSegundo*2)
limiters[ip] = lim
}
mu.Unlock()
if !lim.Allow() {
http.Error(w, `{"erro":"limite excedido"}`, http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
O rate limiter por IP usa um map protegido por mutex para manter um limiter separado por endereço. Em produção, considere usar um cache com TTL para evitar crescimento ilimitado do map.
Middleware de panic recovery
Recovery previne que um panic em um handler derrube todo o servidor:
func comRecovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// Captura stack trace
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
slog.Error("panic recuperado",
"erro", err,
"path", r.URL.Path,
"metodo", r.Method,
"stack", string(buf[:n]),
)
http.Error(w, `{"erro":"erro interno do servidor"}`,
http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
O uso de defer com recover é o mecanismo padrão de Go para capturar panics. Esse middleware deve ser o primeiro na cadeia para capturar panics de qualquer ponto da requisição.
Encadeamento de middlewares
Middlewares são compostos aninhando chamadas de função:
// Composição manual (de dentro para fora)
handler := comRecovery(comLogging(comCORS("*")(comAuth(mux))))
// Composição com helper (mais legível)
func encadear(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
// Aplica na ordem inversa para que o primeiro middleware
// seja o primeiro a processar a requisição
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// Uso com helper
handler := encadear(mux,
comRecovery,
comLogging,
comCORS("*"),
comRateLimit(100),
)
A função encadear aplica middlewares na ordem inversa porque cada middleware envolve o anterior. O resultado é que a execução acontece de cima para baixo: recovery -> logging -> CORS -> rate limit -> handler.
Middleware seletivo por rota
Nem todo middleware deve ser aplicado globalmente. Você pode aplicar middlewares em rotas específicas:
func main() {
mux := http.NewServeMux()
// Rotas públicas (sem auth)
mux.HandleFunc("GET /api/saude", saudeHandler)
mux.HandleFunc("POST /api/login", loginHandler)
// Rotas protegidas (com auth)
mux.Handle("GET /api/perfil", comAuth(http.HandlerFunc(perfilHandler)))
mux.Handle("GET /api/admin", comAuth(comAdmin(http.HandlerFunc(adminHandler))))
// Middlewares globais
handler := encadear(mux, comRecovery, comLogging)
http.ListenAndServe(":8080", handler)
}
Esse padrão permite granularidade fina: middlewares globais no servidor, middlewares por grupo de rotas e middlewares por rota individual. Consulte o glossário de router para padrões avançados de roteamento.
Testando middlewares
Middlewares são testáveis isoladamente porque são funções puras:
func TestComLogging(t *testing.T) {
// Handler fake que retorna 200
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})
// Aplica middleware
handler := comLogging(inner)
req := httptest.NewRequest(http.MethodGet, "/teste", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("esperava 200, recebeu %d", rec.Code)
}
}
func TestComAuth_SemToken(t *testing.T) {
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Error("handler não deveria ser chamado sem token")
})
handler := comAuth(inner)
req := httptest.NewRequest(http.MethodGet, "/protegido", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusUnauthorized {
t.Errorf("esperava 401, recebeu %d", rec.Code)
}
}
func TestComAuth_ComToken(t *testing.T) {
chamou := false
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
chamou = true
w.WriteHeader(http.StatusOK)
})
handler := comAuth(inner)
req := httptest.NewRequest(http.MethodGet, "/protegido", nil)
req.Header.Set("Authorization", "Bearer token-valido")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if !chamou {
t.Error("handler deveria ter sido chamado com token válido")
}
}
Para testing avançado de middlewares com benchmarks de performance, consulte o tutorial de testes em Go.
Middleware com OpenTelemetry
Em produção, observabilidade é crucial. Aqui está um middleware que integra com OpenTelemetry:
func comTracing(serviceName string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, span := otel.Tracer(serviceName).Start(r.Context(), r.URL.Path)
defer span.End()
span.SetAttributes(
attribute.String("http.method", r.Method),
attribute.String("http.url", r.URL.String()),
)
rec := &responseRecorder{ResponseWriter: w, statusCode: 200}
next.ServeHTTP(rec, r.WithContext(ctx))
span.SetAttributes(
attribute.Int("http.status_code", rec.statusCode),
)
})
}
}
Perguntas Frequentes
Qual a ordem correta dos middlewares?
A ordem recomendada é: Recovery (primeiro, captura panics de tudo) -> Logging (registra todas as requisições) -> CORS (responde preflight antes de outros middlewares) -> Rate Limiting (rejeita excesso antes de processamento) -> Autenticação (valida credenciais) -> Handler. O recovery deve sempre ser o mais externo, e a autenticação o mais próximo do handler.
Middleware em Go é diferente de middleware em Express/Django?
O conceito é o mesmo — lógica que intercepta requisições — mas a implementação em Go é mais explícita. Em vez de um pipeline implícito gerenciado pelo framework, em Go você compõe funções manualmente. Isso dá controle total sobre a ordem de execução e evita comportamentos surpreendentes. O padrão func(http.Handler) http.Handler é equivalente ao next() de Express.
Preciso de um framework para usar middlewares?
Não. O padrão de middleware em Go funciona nativamente com net/http. Frameworks como Chi e Echo oferecem helpers como agrupamento de rotas com middlewares específicos, mas o mecanismo subjacente é idêntico. Se usar apenas a stdlib, a função encadear mostrada neste artigo resolve a composição. Consulte o tutorial de API REST em Go para exemplos completos.
Como compartilhar dados entre middleware e handler?
Use context.WithValue para injetar dados no context da requisição e r.Context().Value() para extrair no handler. Sempre defina chaves como tipos privados (type contextKey string) para evitar colisões entre packages. Evite armazenar dados grandes no context — ele é projetado para metadados como IDs de usuário e request IDs, não para payloads.