Go Admin

Guia Completo de Chi Router em Go: Roteamento Idiomático e Middlewares Componíveis Já leu

O que é Chi e Por que Ele Importa Chi é um roteador HTTP leve e expressivo para Go que se destaca por sua arquitetura idiomática e suporte nativo a middlewares compostos. Diferente de outros frameworks web em Go que tentam replicar patterns de linguagens dinâmicas, Chi foi construído desde o início respeitando os princípios de simplicidade e composição que caracterizam a linguagem. Ele implementa a interface , o que significa que qualquer código Chi é compatível com o ecossistema padrão da biblioteca do Go. A proposta central do Chi é oferecer um roteador que não adicione overhead desnecessário, mas que permita escrever rotas de forma clara e com suporte primeiro para middlewares. Você não precisa aprender um "dialeto" especial ou magic frameworks — apenas Go puro, com uma camada de abstração bem pensada. Isso torna Chi particularmente atrativo para quem valoriza legibilidade, testabilidade e performance em aplicações web. Conceitos Fundamentais do Roteamento Estrutura Básica de uma Aplicação Chi Uma

O que é Chi e Por que Ele Importa

Chi é um roteador HTTP leve e expressivo para Go que se destaca por sua arquitetura idiomática e suporte nativo a middlewares compostos. Diferente de outros frameworks web em Go que tentam replicar patterns de linguagens dinâmicas, Chi foi construído desde o início respeitando os princípios de simplicidade e composição que caracterizam a linguagem. Ele implementa a interface http.Handler, o que significa que qualquer código Chi é compatível com o ecossistema padrão da biblioteca net/http do Go.

A proposta central do Chi é oferecer um roteador que não adicione overhead desnecessário, mas que permita escrever rotas de forma clara e com suporte primeiro para middlewares. Você não precisa aprender um "dialeto" especial ou magic frameworks — apenas Go puro, com uma camada de abstração bem pensada. Isso torna Chi particularmente atrativo para quem valoriza legibilidade, testabilidade e performance em aplicações web.

Conceitos Fundamentais do Roteamento

Estrutura Básica de uma Aplicação Chi

Uma aplicação Chi começa com a criação de um roteador via chi.NewRouter(). Este objeto é responsável por registrar rotas e middlewares, e pode ser passado diretamente para http.ListenAndServe() porque implementa http.Handler.

package main

import (
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
)

func main() {
    r := chi.NewRouter()

    // Registrar uma rota simples GET
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        fmt.Fprintf(w, "Olá, mundo!")
    })

    http.ListenAndServe(":3000", r)
}

Neste exemplo, estamos criando um roteador que escuta na porta 3000 e responde com uma mensagem na rota raiz. Note que a função handler segue a assinatura padrão func(w http.ResponseWriter, r *http.Request), não há nada proprietário aqui.

Parâmetros de Rota (Route Parameters)

Chi permite capturar parâmetros dinâmicos na URL através da sintaxe {nomeDoParâmetro}. Esses valores são extraídos e podem ser acessados usando chi.URLParam().

package main

import (
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
)

func main() {
    r := chi.NewRouter()

    // Rota com parâmetro dinâmico
    r.Get("/users/{userID}", func(w http.ResponseWriter, r *http.Request) {
        userID := chi.URLParam(r, "userID")
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, `{"user_id": "%s"}`, userID)
    })

    // Rota com múltiplos parâmetros
    r.Get("/posts/{postID}/comments/{commentID}", func(w http.ResponseWriter, r *http.Request) {
        postID := chi.URLParam(r, "postID")
        commentID := chi.URLParam(r, "commentID")
        fmt.Fprintf(w, "Post %s, Comentário %s", postID, commentID)
    })

    http.ListenAndServe(":3000", r)
}

Chi não força tipagem de parâmetros — você recebe strings e decide como validar ou converter. Isso oferece controle fino, embora exija disciplina do desenvolvedor.

Agrupamento de Rotas (Route Groups)

Para aplicações que crescem, é essencial organizar rotas em grupos lógicos. Chi permite isso através do método Group(), que retorna um novo roteador aninhado sobre o qual você pode registrar subrotas e middlewares específicos.

package main

import (
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
)

func main() {
    r := chi.NewRouter()

    // Grupo de rotas para API v1
    r.Route("/api/v1", func(r chi.Router) {
        r.Get("/status", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "API v1 está ativa")
        })

        // Subrotas para usuários
        r.Route("/users", func(r chi.Router) {
            r.Get("/", func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprintf(w, "Lista de usuários")
            })
            r.Post("/", func(w http.ResponseWriter, r *http.Request) {
                fmt.Fprintf(w, "Usuário criado")
            })
            r.Get("/{userID}", func(w http.ResponseWriter, r *http.Request) {
                userID := chi.URLParam(r, "userID")
                fmt.Fprintf(w, "Usuário %s", userID)
            })
        })
    })

    http.ListenAndServe(":3000", r)
}

Este padrão permite que você estruture sua API de forma hierárquica, refletindo a organização conceitual das suas funcionalidades. Cada grupo pode ter seus próprios middlewares, como veremos adiante.

Middlewares: O Coração da Composição

Entendendo Middlewares em Go

Um middleware em Go é simplesmente uma função que recebe um http.Handler e retorna outro http.Handler. Essa estrutura permite que você "envolva" handlers com lógica transversal como autenticação, logging, compressão, etc. Chi facilita o registro e composição desses middlewares.

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
    "github.com/go-chi/chi/v5"
)

// Middleware para logar requisições
func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Iniciando %s %s", r.Method, r.RequestURI)
        next.ServeHTTP(w, r)
        log.Printf("Concluído em %v", time.Since(start))
    })
}

// Middleware para verificar autorização (exemplo simplificado)
func AuthMiddleware(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, "Token ausente", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func main() {
    r := chi.NewRouter()

    // Registrar middlewares globalmente (aplicados em todas as rotas)
    r.Use(LoggingMiddleware)

    r.Get("/public", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Rota pública")
    })

    // Grupo protegido com middleware específico
    r.Route("/protected", func(r chi.Router) {
        r.Use(AuthMiddleware)

        r.Get("/data", func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintf(w, "Dados privados")
        })
    })

    http.ListenAndServe(":3000", r)
}

Note como Use() registra um middleware, e quando você chama Use() dentro de um Route(), o middleware aplica-se apenas àquele grupo e suas subrotas. Isso é composição idiomática em Go.

Middlewares Compostos e Contexto

Chi integra-se perfeitamente com context.Context, permitindo que middlewares transmitam dados para handlers através do contexto da requisição. Esse padrão evita dependências globais e torna o fluxo de dados explícito.

package main

import (
    "context"
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
)

// Tipo para armazenar valores no contexto
type ctxKey string

const userIDKey ctxKey = "userID"

// Middleware que extrai e valida um ID de usuário
func UserExtractorMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        userID := r.Header.Get("X-User-ID")
        if userID == "" {
            http.Error(w, "X-User-ID obrigatório", http.StatusBadRequest)
            return
        }

        // Armazenar o userID no contexto
        ctx := context.WithValue(r.Context(), userIDKey, userID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

func main() {
    r := chi.NewRouter()

    r.Route("/api", func(r chi.Router) {
        r.Use(UserExtractorMiddleware)

        r.Get("/profile", func(w http.ResponseWriter, r *http.Request) {
            // Recuperar o userID do contexto
            userID := r.Context().Value(userIDKey).(string)
            fmt.Fprintf(w, "Perfil do usuário: %s", userID)
        })
    })

    http.ListenAndServe(":3000", r)
}

Este padrão é fundamental em Go moderno. O contexto flui através de toda a cadeia de middleware e handler, permitindo que lógica compartilhada seja injetada sem acoplamento.

Middlewares de Terceiros e Padrões Comuns

Chi é compatível com middlewares populares do ecossistema Go. A comunidade mantém um conjunto de middlewares úteis e testados que podem ser usados diretamente.

package main

import (
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()

    // Middlewares do próprio Chi para tarefas comuns
    r.Use(middleware.RequestID)      // Gera ID único para cada requisição
    r.Use(middleware.RealIP)         // Extrai IP real (útil com proxies)
    r.Use(middleware.Logger)         // Logging estruturado
    r.Use(middleware.Recoverer)      // Recupera-se de panics

    r.Get("/test", func(w http.ResponseWriter, r *http.Request) {
        // RequestID está disponível via middleware.GetReqID(r.Context())
        reqID := middleware.GetReqID(r.Context())
        fmt.Fprintf(w, "Request ID: %s", reqID)
    })

    http.ListenAndServe(":3000", r)
}

Os middlewares embutidos no Chi (github.com/go-chi/chi/v5/middleware) cobrem cenários comuns como logging, recuperação de erros e extração de metadados de requisição. Você pode combinar esses com seus próprios middlewares.

Padrões Avançados e Organização

RESTful API Completa com Chi

Chi brilha quando você precisa construir APIs RESTful bem estruturadas. Combinando agrupamento de rotas com middlewares, é possível criar aplicações limpas e manuteníveis.

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Simulação de banco de dados
var users = map[string]User{
    "1": {ID: "1", Name: "Alice", Email: "alice@example.com"},
    "2": {ID: "2", Name: "Bob", Email: "bob@example.com"},
}

func GetUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
    var user User
    json.NewDecoder(r.Body).Decode(&user)
    users[user.ID] = user
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func GetUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "userID")
    user, exists := users[userID]
    if !exists {
        http.Error(w, "Usuário não encontrado", http.StatusNotFound)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func UpdateUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "userID")
    var user User
    json.NewDecoder(r.Body).Decode(&user)
    user.ID = userID
    users[userID] = user
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func DeleteUser(w http.ResponseWriter, r *http.Request) {
    userID := chi.URLParam(r, "userID")
    delete(users, userID)
    w.WriteHeader(http.StatusNoContent)
}

func main() {
    r := chi.NewRouter()

    // Middlewares globais
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    // Rotas de healthcheck
    r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "OK")
    })

    // API v1
    r.Route("/api/v1", func(r chi.Router) {
        r.Route("/users", func(r chi.Router) {
            r.Get("/", GetUsers)
            r.Post("/", CreateUser)

            r.Route("/{userID}", func(r chi.Router) {
                r.Get("/", GetUser)
                r.Put("/", UpdateUser)
                r.Delete("/", DeleteUser)
            })
        })
    })

    http.ListenAndServe(":3000", r)
}

Neste exemplo, a estrutura RESTful é clara: usuários podem ser listados, criados, recuperados individualmente, atualizados e deletados. Cada operação tem seu próprio handler, e as rotas refletem perfeitamente a semântica HTTP.

Middlewares Reutilizáveis com Dependências

Em aplicações reais, middlewares frequentemente precisam acessar serviços ou configurações. Chi permite criar middlewares que aceitam parâmetros e retornam o middleware propriamente dito.

package main

import (
    "context"
    "database/sql"
    "fmt"
    "net/http"
    "github.com/go-chi/chi/v5"
)

// Simular um serviço de autenticação
type AuthService struct {
    apiKey string
}

func (as *AuthService) ValidateToken(token string) bool {
    return token == as.apiKey
}

// Factory para criar middleware com injeção de dependência
func AuthMiddlewareFactory(authService *AuthService) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := r.Header.Get("Authorization")
            if !authService.ValidateToken(token) {
                http.Error(w, "Token inválido", http.StatusUnauthorized)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

// Middleware que injeta um banco de dados no contexto
func DatabaseMiddleware(db *sql.DB) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ctx := context.WithValue(r.Context(), "db", db)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

func main() {
    // Simular inicialização de serviços
    authService := &AuthService{apiKey: "secret123"}
    // db := connectToDatabase() // em uma aplicação real

    r := chi.NewRouter()

    // Registrar middlewares factory com dependências
    r.Use(AuthMiddlewareFactory(authService))
    // r.Use(DatabaseMiddleware(db))

    r.Get("/protected", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Recurso protegido acessado com sucesso")
    })

    http.ListenAndServe(":3000", r)
}

Este padrão é essencial em aplicações que crescem: a injeção de dependência garante que seus middlewares permaneçam testáveis e desacoplados da implementação específica dos serviços.

Conclusão

Aprender Chi significa aprender a escrever código Go idiomático para construir APIs web. Os três pontos principais que você deve levar desta leitura são: primeiro, roteamento em Chi é simples e composável porque respeita a interface padrão http.Handler, mantendo compatibilidade com todo o ecossistema Go; segundo, middlewares são funções de primeira classe que permitem cruzar responsabilidades (autenticação, logging, validação) de forma elegante e reutilizável através do contexto; terceiro, a organização em grupos de rotas (Route()) aliada ao suporte a middlewares por grupo permite que você cresça de uma aplicação simples para uma API complexa sem refatorações radicais.

Chi não é um framework monolítico — é uma ferramenta focada que você compõe com outras bibliotecas do Go. Isso exige disciplina, mas oferece flexibilidade e clareza que frameworks "tudo incluído" dificilmente conseguem.

Referências


Artigos relacionados