Avoiding Catchall Pitfalls on the Root Route with Go’s ServeMux
 George Rodier
George Rodier 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- /about2,- /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- /aboutand- /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.