Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I’ll earn a commission. Please notice your final price is not affected at all by using those links.
gorilla/mux was my de-facto package to use when implementing HTTP routers, sadly the maintainers have decided to move on and archive the toolkit, security-wise this isn’t a problem at the moment but it could be because depending on a third party package that is not longer maintained is always risk.
Migrating from the gorilla/mux
router to another one will depend on how the application was implemented and the architecture that was selected, if everything is clearly separated it shouldn’t be that difficult to make that change, however this could be something time consuming depending on the application size.
There are a lot of different routers out there, however in this post I will focus on three specifically:
The goal is to evaluate those three packages, measure the amount of work and actually migrate to the selected package. This could give you a bit of idea regarding the amount of effort to take when doing something similar.
In this case I’ll use TODO Application example I’ve been using for my videos and specifically the part to be migrated consists of updating internal/rest/task.go, specifically Register
method:
42func (t *TaskHandler) Register(r *mux.Router) {
43 r.HandleFunc("/tasks", t.create).Methods(http.MethodPost)
44 r.HandleFunc(fmt.Sprintf("/tasks/{id:%s}", uuidRegEx), t.task).Methods(http.MethodGet)
45 r.HandleFunc(fmt.Sprintf("/tasks/{id:%s}", uuidRegEx), t.update).Methods(http.MethodPut)
46 r.HandleFunc(fmt.Sprintf("/tasks/{id:%s}", uuidRegEx), t.delete).Methods(http.MethodDelete)
47 r.HandleFunc("/search/tasks", t.search).Methods(http.MethodPost)
48}
And all the those methods, they use mux#Router.HandleFunc implementing the following contract:
f func(http.ResponseWriter, *http.Request)
Which in practice is like net/http#HandlerFunc:
type HandlerFunc func(http.ResponseWriter, *http.Request)
Ideally a package that supports something similar should be the easier to migrate to. Let’s try.
Migrating to gin-gonic/gin
gin-gonic/gin
does not support that contract, instead it defines its own using a *gin.Context
as the received parameter for each handler, something like:
type HandlerFunc func(*gin.Context)
Using gin-gonic/gin
in our application has some pros:
- Removes explicit JSON rendering,
- Replaces explicit HTTP verb to use with a method instead, and
- Officially supported by OpenTelemetry effectively replacing the one we used for
gorilla/mux
before.
However as con we:
- Lost reg-ex support (this is intentionally not present)
The final code representing this migration is available here.
Migrating to go-chi/chi
go-chi/chi
uses the standard library and has zero dependencies, it has a few pros:
- Supports the same contract we used previously,
- Removes explicit JSON rendering, and
- Replaces explicit HTTP verb to use with a method instead.
However as con:
- It’s not officially supported by OpenTelemetry, so another third party package has to be used.
The final code representing this migration is available here.
Migrating to labstack/echo
labstack/echo
uses its own contract to define handler, similar to gin
it uses its own context however it requires an error to be returned back, a quick way to stop calls after rendering results.
func(c echo.Context) error
Using labstack/echo
in our application has some pros:
- Removes explicit JSON rendering,
- Replaces explicit HTTP verb to use with a method instead, and
- Officially supported by OpenTelemetry effectively replacing the one we used for
gorilla/mux
before.
However as con we:
- Lost reg-ex support, and
- Require to make more changes than the previous alternatives.
The final code representing this migration is available here.
Conclusion
It was quite interesting to evaluate new routers now that gorilla/mux
is no longer maintained, I originally chose mux
because I wanted to stay as close as possible to the standard library and I don’t want to lose that when migrating to a new package. For this application I think migrating to chi
makes the most sense for two reasons:
- Keeps the same APIs to define handlers, and
- Has zero dependencies.
And although gin
and echo
provide ways to wrap http.Handler
or http.HandlerFun
into their own versions, I think keeping it closer to the standard library makes more sense. All three projects are good routers, even some provide more than just that and could be called actual frameworks, in the end choosing one or the other depends on the trade-offs, specifically to my use case for this API:
chi
simplest, no dependencies and standard library-like routes.gin
it’s the faster (according to their benchmarks), however brings more dependencies and their own opinionated way to implement routes,echo
has the highest lock-in, however it seems to have consistent sponsors; defines their own opinionated way to implement routes.
I will keep an eye out for their evolution, I can see recommending one or the other depending on the API to be built, the features to make available to the customers and the deadlines.
Recommended reading
If you’re looking to sink your teeth into more Software Architecture, I recommend the following content:
- Post: Quality Attributes / Non-Functional Requirements
- Book: Hands-On Software Architecture with Golang
- Book: Building Evolutionary Architectures: Support Constant Change - Post
- Book: Practical Cloud Security: A Guide for Secure Design and Deployment