O que é Handler em Go?

Um handler em Go é qualquer valor que implementa a interface http.Handler, definida no pacote net/http. Essa interface possui um único métodoServeHTTP(ResponseWriter, *Request) — e é o bloco fundamental para construir aplicações web em Go. Todo processamento de requisições HTTP, desde servir uma página estática até executar lógica complexa de negócios, passa por handlers.

A simplicidade dessa interface é uma decisão de design deliberada. Ao invés de classes pesadas com dezenas de métodos (como controllers em frameworks MVC tradicionais), Go define o contrato mínimo necessário: receba uma requisição, escreva uma resposta. Essa abordagem composicional permite que handlers sejam combinados, encadeados e testados de forma independente — um padrão que escala desde scripts simples até sistemas distribuídos como os que rodam no Google.

O pacote net/http também fornece http.HandlerFunc, um tipo adaptador que transforma qualquer função com a assinatura correta em um handler completo. Isso elimina a necessidade de criar structs dedicadas para cada endpoint, tornando o código mais conciso sem perder flexibilidade.

A interface http.Handler

A interface http.Handler é definida assim:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Qualquer type que implemente ServeHTTP satisfaz essa interface. Aqui está um exemplo com uma struct:

type SaudeHandler struct {
    versao string
    inicio time.Time
}

func (h *SaudeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    uptime := time.Since(h.inicio).String()
    fmt.Fprintf(w, `{"status":"ok","versao":"%s","uptime":"%s"}`, h.versao, uptime)
}

func main() {
    saude := &SaudeHandler{
        versao: "1.0.0",
        inicio: time.Now(),
    }

    mux := http.NewServeMux()
    mux.Handle("/saude", saude)

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

Usar structs como handlers é ideal quando você precisa injetar dependências — conexões de banco, loggers, configurações — sem variáveis globais. Esse padrão é detalhado no tutorial de Clean Architecture em Go.

http.HandlerFunc — o adaptador funcional

Para casos simples, criar uma struct para cada endpoint é excessivo. http.HandlerFunc resolve isso:

// Definição na stdlib
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

O HandlerFunc é um tipo que implementa http.Handler automaticamente. Na prática, você raramente o usa diretamente — http.HandleFunc faz a conversão implícita:

func main() {
    mux := http.NewServeMux()

    // Registro direto com HandleFunc (converte automaticamente)
    mux.HandleFunc("GET /usuarios", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprint(w, `[{"nome":"Ana"},{"nome":"Carlos"}]`)
    })

    // Ou com função nomeada
    mux.HandleFunc("POST /usuarios", criarUsuario)

    // Registro explícito com Handle (precisa implementar http.Handler)
    mux.Handle("GET /saude", http.HandlerFunc(verificarSaude))

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

func criarUsuario(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()
    var usuario struct {
        Nome  string `json:"nome"`
        Email string `json:"email"`
    }
    if err := json.NewDecoder(r.Body).Decode(&usuario); err != nil {
        http.Error(w, "JSON inválido", http.StatusBadRequest)
        return
    }
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(usuario)
}

func verificarSaude(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "OK")
}

ServeMux — o router da stdlib (Go 1.22+)

A partir do Go 1.22, o ServeMux ganhou suporte nativo a method routing e path parameters, eliminando a necessidade de routers externos para muitos casos:

func main() {
    mux := http.NewServeMux()

    // Method routing — especifique o método HTTP antes do path
    mux.HandleFunc("GET /api/produtos", listarProdutos)
    mux.HandleFunc("POST /api/produtos", criarProduto)
    mux.HandleFunc("GET /api/produtos/{id}", buscarProduto)
    mux.HandleFunc("PUT /api/produtos/{id}", atualizarProduto)
    mux.HandleFunc("DELETE /api/produtos/{id}", deletarProduto)

    // Wildcard — captura tudo após o prefixo
    mux.HandleFunc("GET /arquivos/{path...}", servirArquivos)

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

func buscarProduto(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id") // Extrai o parâmetro {id}
    w.Header().Set("Content-Type", "application/json")
    fmt.Fprintf(w, `{"id":"%s","nome":"Produto %s"}`, id, id)
}

func servirArquivos(w http.ResponseWriter, r *http.Request) {
    caminho := r.PathValue("path") // Captura tudo: "docs/guia/intro.md"
    fmt.Fprintf(w, "Servindo arquivo: %s", caminho)
}

Para mais detalhes sobre as novidades de routing, consulte o glossário de router e o artigo sobre as novidades do Go 1.22.

Handlers com dependências — Injeção via struct

O padrão mais robusto para handlers em produção é injetar dependências via struct:

type UsuarioHandler struct {
    DB     *sql.DB
    Logger *slog.Logger
    Cache  *redis.Client
}

func (h *UsuarioHandler) Listar(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    h.Logger.Info("listando usuários", "metodo", r.Method)

    rows, err := h.DB.QueryContext(ctx, "SELECT id, nome, email FROM usuarios")
    if err != nil {
        h.Logger.Error("erro no banco", "err", err)
        http.Error(w, "Erro interno", http.StatusInternalServerError)
        return
    }
    defer rows.Close()

    var usuarios []Usuario
    for rows.Next() {
        var u Usuario
        if err := rows.Scan(&u.ID, &u.Nome, &u.Email); err != nil {
            continue
        }
        usuarios = append(usuarios, u)
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(usuarios)
}

func (h *UsuarioHandler) Buscar(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    ctx := r.Context()

    var u Usuario
    err := h.DB.QueryRowContext(ctx, "SELECT id, nome, email FROM usuarios WHERE id = $1", id).
        Scan(&u.ID, &u.Nome, &u.Email)
    if err == sql.ErrNoRows {
        http.Error(w, "Usuário não encontrado", http.StatusNotFound)
        return
    }
    if err != nil {
        http.Error(w, "Erro interno", http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(u)
}

// Registro das rotas
func main() {
    db, _ := sql.Open("postgres", "...")
    logger := slog.Default()

    uh := &UsuarioHandler{DB: db, Logger: logger}

    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/usuarios", uh.Listar)
    mux.HandleFunc("GET /api/usuarios/{id}", uh.Buscar)

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

Esse padrão é a base da arquitetura limpa em Go, e funciona perfeitamente com testes porque você pode injetar mocks de banco e logger.

Testando handlers

O pacote httptest torna o teste de handlers simples e direto:

func TestBuscarProduto(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/api/produtos/42", nil)
    req.SetPathValue("id", "42") // Go 1.22+
    rec := httptest.NewRecorder()

    buscarProduto(rec, req)

    if rec.Code != http.StatusOK {
        t.Errorf("esperava 200, recebeu %d", rec.Code)
    }

    var resp map[string]string
    json.Unmarshal(rec.Body.Bytes(), &resp)
    if resp["id"] != "42" {
        t.Errorf("esperava id '42', recebeu '%s'", resp["id"])
    }
}

// Teste de integração com servidor completo
func TestAPIIntegracao(t *testing.T) {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/produtos/{id}", buscarProduto)

    srv := httptest.NewServer(mux)
    defer srv.Close()

    resp, err := http.Get(srv.URL + "/api/produtos/99")
    if err != nil {
        t.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        t.Errorf("esperava 200, recebeu %d", resp.StatusCode)
    }
}

Para técnicas avançadas de testing incluindo mocks e benchmarks, consulte o tutorial de TDD com CI/CD.

Composição de handlers

Handlers podem ser compostos usando middleware — funções que envolvem outros handlers:

// Middleware de logging
func comLogging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        inicio := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("requisição",
            "metodo", r.Method,
            "path", r.URL.Path,
            "duracao", time.Since(inicio),
        )
    })
}

// Middleware de autenticação
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, "Não autorizado", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Composição
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/dados", buscarDados)

    // Encadeia: logging -> auth -> handler
    handler := comLogging(comAuth(mux))
    http.ListenAndServe(":8080", handler)
}

Para uma exploração completa de padrões de middleware em Go, consulte a entrada dedicada no glossário.

Boas práticas para handlers

  1. Um handler, uma responsabilidade — separe validação, lógica de negócios e formatação de resposta
  2. Injete dependências via struct — evite variáveis globais e package-level state
  3. Sempre feche r.Body — use defer r.Body.Close() para evitar vazamentos
  4. Retorne após erros — use return após http.Error() para não continuar processando
  5. Use context — propague o r.Context() para todas as operações de I/O
  6. Teste com httptest — testes unitários rápidos sem iniciar servidor real
  7. Documente com slog — log estruturado facilita debugging em produção
  8. Use interfaces para dependências — facilita mocks em testes

Perguntas Frequentes

Qual a diferença entre http.Handler e http.HandlerFunc?

http.Handler é uma interface que exige a implementação do método ServeHTTP(ResponseWriter, *Request). http.HandlerFunc é um tipo adaptador que transforma qualquer função com essa assinatura em um http.Handler automaticamente. Na prática, use HandleFunc para endpoints simples e Handle com structs quando precisar de injeção de dependências.

Como passar dados entre middleware e handler?

Use o context da requisição. No middleware, adicione valores com context.WithValue(r.Context(), chave, valor) e crie uma nova requisição com r.WithContext(ctx). No handler, extraia com r.Context().Value(chave). Para dados tipados, defina tipos privados como chaves para evitar colisões.

Preciso de um framework web para usar handlers em Go?

Não. O pacote net/http da stdlib, especialmente a partir do Go 1.22 com method routing e path parameters no ServeMux, é suficiente para a maioria das aplicações. Frameworks como Chi e Echo são wrappers finos sobre a stdlib que adicionam conveniências, mas não são necessários. Comece com a stdlib e migre para frameworks apenas se sentir falta de funcionalidades específicas.

Como testar handlers que dependem de banco de dados?

Injete a dependência de banco como uma interface na struct do handler. Nos testes, crie mocks que implementam essa interface. Alternativamente, use Testcontainers para testes de integração com um banco real em container Docker. Para testes unitários rápidos, httptest com mocks é o padrão recomendado.