Como utilizar go-chi para rotas e middleware

Durante muito tempo, gorilla/mux era o meu router favorito na hora de escrever APIs. Porém, desde que fiz o post sobre benchmark comparando gorilla/mux e go-chi (link para o post), meu router favorito tem sido o go-chi, pois sua performance é bem superior. E para ajudar, recentemente o projeto do gorilla/mux ficou sem mantenedor. ☹️

Por isso, resolvi fazer esse post para mostrar tudo o que você pode fazer com go-chi.

Para começar, vamos escrever um código muito simples para criar uma rota com o verbo GET.

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
)

func main() {
    r := chi.NewRouter()
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("welcome"))
    })
    http.ListenAndServe(":3000", r)
}

Ao fazer o chi.NewRouter(), nossa variável r irá receber uma struct do tipo chi.Mux. Essa struct, dentre seus vários métodos, tem um método para cada um dos verbos HTTP.

func main() {
    r := chi.NewRouter()
    r.Get("/posts", HandleFunc)
    r.Get("/posts/{id}", HandleFunc)
    r.Post("/posts", HandleFunc)
    r.Put("/posts/{id}", HandleFunc)
    r.Patch("/posts/{id}", HandleFunc)
    r.Delete("/posts/{id}", HandleFunc)

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

Como podemos ver no exemplo acima, para definir parâmetros em suas rotas, tudo que precisamos fazer é adicionar o nome do parâmetro entre colchetes.

Para obter o valor do id dentro da função que irá tratar a request, basta utilizar a função chi.URLParam(r, "id"), onde r é o nosso *http.Request e o “id” o nome do parâmetro que foi definido na rota.

Para um sistema pequeno e com poucas rotas, definir cada rota em uma linha não gera grandes problemas. No entanto, para sistemas com muitas rotas, vai ficando cada vez mais complicado manter e dar manutenção.

Além da forma como vimos até agora, o go-chi nos disponibiliza duas outras formas para declarar nossas rotas, porém de uma forma um pouco mais organizada.

A primeira é utilizando sub-rotas, o que também é muito útil quando você separa sua api em versões.

func main() {
    r := chi.NewRouter()
    r.Route("posts", func(r chi.Router) {
        r.Get("/", HandleFunc)
        r.Get("/{id}", HandleFunc)
        r.Post("/", HandleFunc)
        r.Put("/{id}", HandleFunc)
        r.Patch("/{id}", HandleFunc)
        r.Delete("/{id}", HandleFunc)
    })

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

A segunda é separando suas rotas em funções, o que pode ajudar muito, já que você pode definir as rotas dentro dos packages onde estão as funções que vão tratar as requests.

// função no package main
func main() {
    r := chi.NewRouter()
    r.Route("posts", posts.Router)

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

// função no package posts
func Router(r chi.Router) {
    r.Get("/", HandleFunc)
    r.Get("/{id}", HandleFunc)
    r.Post("/", HandleFunc)
    r.Put("/{id}", HandleFunc)
    r.Patch("/{id}", HandleFunc)
    r.Delete("/{id}", HandleFunc)
}

Um último método que quero abordar ainda na parte de rotas é o método Mount. A primeira vista esse método pode ser muito parecido com o método Route, já que ele também é utilizado para criar sub-rotas. Porém, a maior diferença entre eles é que o método Mount recebe uma outra struct do tipo chi.Mux, ou seja, um router completamente novo e diferente do principal.

// função no package main
func main() {
    r := chi.NewRouter()
    r.Mount("posts", posts.Router())

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

// função no package posts
func Router() http.Handler {
    r := chi.NewRouter()

    r.Get("/", HandleFunc)
    r.Get("/{id}", HandleFunc)
    r.Post("/", HandleFunc)
    r.Put("/{id}", HandleFunc)
    r.Patch("/{id}", HandleFunc)
    r.Delete("/{id}", HandleFunc)
	
    return r
}

E como nem só de rotas e tratamentos de requests vive um router, vamos falar um pouco sobre middleware.

Se esse termo é novo para você, de forma bem simples, middleware é uma função executada em todos os requests entre o recebimento da request e o tratamento dela pela função destino da rota.

Por padrão, o go-chi vem com vários middleware prontos para serem utilizados.

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
)

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("welcome"))
    })
    http.ListenAndServe(":3000", r)
}

Abaixo vou deixar uma lista com alguns dos middleware que existem no package no momento em que escrevo esse post:

NomeDescrição
AllowContentEncoding(…types)Retorna 415 Unsupported Media Type quando o Content-Encode da request não estiver na lista permitida
AllowContentType(…types)Retorna 415 Unsupported Media Type quando o Content-Type da request não estiver na lista permitida
BasicAuthImplementação para lidar com BasicAuth
Compress(level, …types)Comprimi a response para os tipos determinados. Level 5 de compressão é o máximo aceitável. mais detalhes (https://github.com/go-chi/chi/blob/master/middleware/compress.go)
ContentCharset(…charsets)Retorna 415 Unsupported Media Type quando o charset da request não está na lista de charset permitido
CleanPathLimpa duplicação de / na rota
GetHeadRedireciona automaticamente requests HEAD não encontradas para os handlers de requisições GET
Heartbeat(endpoint)Cria uma rota para fazer healthcheck da aplicação
LoggerLoga inicio e fim da request com o tempo gasto para resposta (esse middleware precisa vir antes do Recoverer)
NoCacheAdicionar um header na response para evitar que o cliente faça cache da response
ProfilerAdiciona de forma simple o net/http/pprof ao router
RealIPSeta o http.Request RemoteAddr para o valor de X-Real-IP ou X-Forwarded-For
RecovererAbsorve panics e printa no stack trace
RequestIDAdiciona um ID para cada request dentro do context da request

Você pode verificar todos os middleware disponíveis em https://github.com/go-chi/chi#core-middlewares ou https://github.com/go-chi/chi/tree/master/middleware.

Caso você queira escrever um middleware customizado, é só criar uma função com a assinatura func(http.Handler) http.Handler e depois utilizá-la em seu router.

func main() {
    r := chi.NewRouter()
    r.Use(middleware.Logger)
    r.Use(MeuMiddleware)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("welcome"))
    })
    http.ListenAndServe(":3000", r)
}

func MeuMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "user", "123")
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

E é isso! Me diz aí nos comentários que router você tem utilizado.

Obrigado por ter lido e até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Deixe uma resposta