Site icon Golang Libraries, Apps, Golang Jobs and Go Tutorials

Which Golang router to use for what cases?

Golang http router

When you start to build web applications with Go, one of the first questions you’ll probably ask is 

“which golang router should I use?”

It’s not an easy question to answer, either. Probably more than 100 different routers are available, all with different APIs, features, and behaviors. So for this blog post I’ve evaluated 30 popular ones, and created a shortlist of the best options along with a flowchart that you can use to help guide your choice.

Before we start, a few notes on terminology:

Note: From a software engineering perspective, conflicting routes are a bad thing. They can be a source of bugs and confusion, and you should generally try to avoid them in your applications.

Shortlisted Golang router packages

Four different routers make the shortlist. They are http.ServeMuxjulienschmidt/httproutergo-chi/chi and gorilla/mux. All four are well-tested, well-documented, and actively maintained. They (mostly) have stable APIs, and are compatible with http.Handlerhttp.HandlerFunc, and the standard middleware pattern.

Regarding speed, all four routers are fast enough for (almost) every application, and I recommend choosing between them based on the specific features you need rather than performance. I’ve personally used all four in-production applications at different times and have been happy with them.

Golang http.ServeMux

I’ll start by saying that if you can use http.ServeMux, you probably should.

As part of the Go standard library, it’s very battle tested and well documented. Using it means that you don’t need to import any third-party dependencies, and most other Go developers will also be familiar how it works. The Go 1 compatibility promise also means that you should be able to rely on http.ServeMux working the same way in the long-term. All of those things are big positives in terms of application maintenance.

Unlike most other routers, it also supports host-based routes, incoming request URLs are automatically sanitized, and the way that it matches routes is smart too: longer route patterns always take precedence over shorter ones. This has the nice side-effect that you can register patterns in any order and it won’t change how your application behaves.

The two main limitations of http.ServeMux are that it doesn’t support method-based routing or variables in URL paths. But the lack of support for method-based routing isn’t always a good reason to avoid it — it’s quite easy to work around with some code like this:

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", index)

    err := http.ListenAndServe(":3000", mux)

func index(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/" {
        http.NotFound(w, r)

    // Common code for all requests can go here...

    switch r.Method {
    case http.MethodGet:
        // Handle the GET request...

    case http.MethodPost:
        // Handle the POST request...

    case http.MethodOptions:
        w.Header().Set("Allow", "GET, POST, OPTIONS")

        w.Header().Set("Allow", "GET, POST, OPTIONS")
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)

In those few lines, you’ve effectively got method-based routing, along with custom 404 and 405 responses and support for OPTIONS requests. That’s a lot more than you get with many third-party routers.

Over time, I’ve come to realize that http.ServeMux has a lot of positives and it’s perfectly sufficient in many cases. In fact, the only time I’d recommend not using it is when you need support for variables in URL paths or custom routing rules. In those cases, trying to work with http.ServeMux can get a bit hairy, and I think it’s generally better to opt for a third-party router.

Exit mobile version