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:
Nome | Descriçã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 |
BasicAuth | Implementaçã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 |
CleanPath | Limpa duplicação de / na rota |
GetHead | Redireciona automaticamente requests HEAD não encontradas para os handlers de requisições GET |
Heartbeat(endpoint) | Cria uma rota para fazer healthcheck da aplicação |
Logger | Loga inicio e fim da request com o tempo gasto para resposta (esse middleware precisa vir antes do Recoverer) |
NoCache | Adicionar um header na response para evitar que o cliente faça cache da response |
Profiler | Adiciona de forma simple o net/http/pprof ao router |
RealIP | Seta o http.Request RemoteAddr para o valor de X-Real-IP ou X-Forwarded-For |
Recoverer | Absorve panics e printa no stack trace |
RequestID | Adiciona 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!