Go Admin

Como Usar Construindo APIs REST em Go sem Framework: Roteamento e Middlewares em Produção Já leu

Fundamentos de APIs REST em Go Puro 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 ( ) 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 é suficientemente poderoso e maduro para aplicações em produção. O principal conceito que você precisa compreender é como o trabalha. Quando você inicia um servidor HTTP em Go, você está basicamente registrando handlers — funções que recebem uma solicitação HTTP e escrevem uma resposta. Esses handlers seguem a interface , que exige apenas um método: . Essa simplicidade é a força de Go: você não está preso a convenções complexas ou configurações mágicas. Vamos começar com o exemplo mais fundamental — um servidor HTTP básico: Quando você executa este código

Fundamentos de APIs REST em Go Puro

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 (net/http) 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 net/http é suficientemente poderoso e maduro para aplicações em produção.

O principal conceito que você precisa compreender é como o net/http trabalha. Quando você inicia um servidor HTTP em Go, você está basicamente registrando handlers — funções que recebem uma solicitação HTTP e escrevem uma resposta. Esses handlers seguem a interface http.Handler, que exige apenas um método: ServeHTTP(ResponseWriter, *Request). Essa simplicidade é a força de Go: você não está preso a convenções complexas ou configurações mágicas.

Vamos começar com o exemplo mais fundamental — um servidor HTTP básico:

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)
}

Quando você executa este código e acessa http://localhost:8080, o handler é invocado. O ResponseWriter permite que você escreva headers e o corpo da resposta, enquanto *Request contém todas as informações sobre a requisição recebida. Simples, mas poderoso.

Roteamento Avançado Sem Framework

O roteamento é a capacidade de mapear diferentes caminhos de URL para diferentes handlers. A abordagem básica usando http.HandleFunc() 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.

Por Que Não Usar o Roteamento Padrão?

O http.HandleFunc() registra rotas de forma estática e não oferece suporte nativo para parâmetros dinâmicos. Se você tentar acessar /usuarios/123, o http.HandleFunc() não consegue extrair o 123 automaticamente. Além disso, ele trata todas as requisições da mesma forma independentemente do método HTTP (GET, POST, etc.).

Vamos construir um roteador personalizado que resolve esses problemas:

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)
}

Neste exemplo, criamos um roteador que:

  1. Armazena rotas com seu padrão, método HTTP e handler associado
  2. Extrai parâmetros analisando a URL — quando encontra :id, sabe que é um parâmetro dinâmico
  3. Implementa http.Handler permitindo que seja usado diretamente com http.ListenAndServe()
  4. Retorna 404 se nenhuma rota corresponder

Teste com curl http://localhost:8080/usuarios/123 e você verá que o 123 é capturado como parâmetro.

Middlewares: Tratamento Transversal de Requisições

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.

Entendendo a Cadeia de Middlewares

Um middleware em Go é simplesmente uma função que recebe um http.Handler e retorna um novo http.Handler. 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.

Vamos construir um sistema robusto de middlewares:

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)
}

Por que a ordem é reversa? Quando aplicamos middlewares, cada um "envolve" o anterior. Se você pensa visualmente:

Requisição → CORS → Autenticação → Logging → Panico → Handler

Mas no código, você aplica na ordem inversa para conseguir esse resultado. Se você tentar acessar curl -H "Authorization: Bearer seu-token-secreto" http://localhost:8080/dados, verá o middleware de CORS permitindo, a autenticação validando o token, o logging registrando a requisição, e o handler finalmente executando.

Contexto de Requisição com Middlewares

Um padrão avançado é usar o contexto (context.Context) para passar dados entre middlewares e handlers. Isso é especialmente útil quando middlewares precisam compartilhar informações:

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)
}

Integração Completa: API REST Funcional

Até agora vimos componentes isolados. Vamos agora montar uma API REST completa que integra roteamento avançado com middlewares robusos:

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))
}

Note que falta import "context" — adicione no início do arquivo. Este exemplo completo demonstra:

  • Roteamento com parâmetros/usuarios/:id extrai o ID da URL
  • Passagem de contexto — Parâmetros são passados via context.Context
  • Middlewares globais — Todos os handlers recebem logging e validação de content-type
  • Autenticação condicional — Apenas rotas POST/PUT/DELETE exigem token
  • Tratamento de erros — Status HTTP apropriados para cada cenário

Teste com:

# 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

Conclusão

Você aprendeu que Go puro oferece poder suficiente para construir APIs REST profissionais sem precisar de frameworks externos. O pacote net/http combinado com padrões bem estruturados (implementar http.Handler, usar middlewares como wrappers de funções, aplicar contexto para compartilhar dados) resulta em código limpo, performático e fácil de testar.

O segundo ponto crucial é que middlewares em Go não são mágicos — 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.

Por fim, você viu que roteamento avançado é construível com menos de 50 linhas de código — 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.

Referências


Artigos relacionados