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!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.