rss linkedin linkedin gitlab github twitter mastodon instagram
Go Tips: gorilla/mux was archived, time to migrate to another router
Dec 19, 2022

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:

  1. gin-gonic/gin,
  2. go-chi/chi and
  3. labstack/echo

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:

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.

If you’re looking to sink your teeth into more Software Architecture, I recommend the following content:


Back to posts