Avoiding Catchall Pitfalls on the Root Route with Go’s ServeMux
When working with Go’s http.ServeMux, you’ll often want to register a homepage at the root route, like this:
mux := http.NewServeMux()
mux.HandleFunc("GET /", handleRootRoute)
But if you fire up your server and start browsing, you might notice something unexpected: your root route matches any path not explicitly registered on the ServeMux. That’s probably not what you wanted, right?
By default, any pattern in Go’s ServeMux ending in a slash (/
) become a catchall for everything underneath it.1 For example:
/about/
matches/about
2,/about/
,/about/foo
, and even/about/foo/bar
.- On the other hand,
/about
(no trailing slash) will only match/about
.
So what happens when you just have a pattern of /
? It effectively catches everything!
The Fix: Restricting Matches with {$}
Luckily, Go provides an escape hatch to match a route with a trailing slash without it being a catchall. Adding the {$}
wildcard to a pattern ensures it only matches specific cases:
/about/{$}
matches/about
and/about/
, but nothing deeper./{$}
ensures your root route only matches/
—and not anything else.
Here’s how you’d fix the earlier example to only match the root route:
mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", handleRootRoute)
Now, requests to /something-else
or /foo/bar
won’t mistakenly fall back to the root route.
Bonus: A Complete Example
Here’s a working example you can test:
package main
import (
"fmt"
"net/http"
)
func handleRootRoute(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Welcome to the root route!")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/{$}", handleRootRoute)
fmt.Println("Starting server on :4000...")
http.ListenAndServe(":4000", mux)
}
Run it, and try visiting /
, /foo
, and /about
. You’ll see the root handler only responds to /
while the other routes respond with a 404 Not Found
.