Routing requests
Having a web application with just one route isn’t very exciting… or useful! Let’s add a couple more routes so that the application starts to shape up like this:
URL Pattern | Handler | Action |
---|---|---|
/ | home | Display the home page |
/snippet/view | snippetView | Display a specific snippet |
/snippet/create | snippetCreate | Create a new snippet |
Reopen the main.go
file and update it as follows:
package main import ( "log" "net/http" ) func home(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello from Snippetbox")) } // Add a snippetView handler function. func snippetView(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Display a specific snippet...")) } // Add a snippetCreate handler function. func snippetCreate(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Create a new snippet...")) } func main() { // Register the two new handler functions and corresponding URL patterns with // the servemux, in exactly the same way that we did before. mux := http.NewServeMux() mux.HandleFunc("/", home) mux.HandleFunc("/snippet/view", snippetView) mux.HandleFunc("/snippet/create", snippetCreate) log.Print("Starting server on :4000") err := http.ListenAndServe(":4000", mux) log.Fatal(err) }
Make sure these changes are saved and then restart the web application:
$ cd $HOME/code/snippetbox $ go run . 2022/01/29 11:16:43 Starting server on :4000
If you visit the following links in your web browser you should now get the appropriate response for each route:


Fixed path and subtree patterns
Now that the two new routes are up and running let’s talk a bit of theory.
Go’s servemux supports two different types of URL patterns: fixed paths and subtree paths. Fixed paths don’t end with a trailing slash, whereas subtree paths do end with a trailing slash.
Our two new patterns — "/snippet/view"
and "/snippet/create"
— are both examples of fixed paths. In Go’s servemux, fixed path patterns like these are only matched (and the corresponding handler called) when the request URL path exactly matches the fixed path.
In contrast, our pattern "/"
is an example of a subtree path (because it ends in a trailing slash). Another example would be something like "/static/"
. Subtree path patterns are matched (and the corresponding handler called) whenever the start of a request URL path matches the subtree path. If it helps your understanding, you can think of subtree paths as acting a bit like they have a wildcard at the end, like "/**"
or "/static/**"
.
This helps explain why the "/"
pattern is acting like a catch-all. The pattern essentially means match a single slash, followed by anything (or nothing at all).
Restricting the root url pattern
So what if you don’t want the "/"
pattern to act like a catch-all?
For instance, in the application we’re building we want the home page to be displayed if — and only if — the request URL path exactly matches "/"
. Otherwise, we want the user to receive a 404 page not found
response.
It’s not possible to change the behavior of Go’s servemux to do this, but you can include a simple check in the home
hander which ultimately has the same effect:
package main ... func home(w http.ResponseWriter, r *http.Request) { // Check if the current request URL path exactly matches "/". If it doesn't, use // the http.NotFound() function to send a 404 response to the client. // Importantly, we then return from the handler. If we don't return the handler // would keep executing and also write the "Hello from SnippetBox" message. if r.URL.Path != "/" { http.NotFound(w, r) return } w.Write([]byte("Hello from Snippetbox")) } ...
Go ahead and make that change, then restart the server and make a request for an unregistered URL path like http://localhost:4000/missing
. You should get a 404
response which looks a bit like this:

The DefaultServeMux
If you’ve been working with Go for a while you might have come across the http.Handle()
and http.HandleFunc()
functions. These allow you to register routes without declaring a servemux, like this:
func main() { http.HandleFunc("/", home) http.HandleFunc("/snippet/view", snippetView) http.HandleFunc("/snippet/create", snippetCreate) log.Print("Starting server on :4000") err := http.ListenAndServe(":4000", nil) log.Fatal(err) }
Behind the scenes, these functions register their routes with something called the DefaultServeMux. There’s nothing special about this — it’s just regular servemux like we’ve already been using, but which is initialized by default and stored in a net/http
global variable. Here’s the relevant line from the Go source code:
var DefaultServeMux = NewServeMux()
Although this approach can make your code slightly shorter, I don’t recommend it for production applications.
Because DefaultServeMux
is a global variable, any package can access it and register a route — including any third-party packages that your application imports. If one of those third-party packages is compromised, they could use DefaultServeMux
to expose a malicious handler to the web.
So, for the sake of security, it’s generally a good idea to avoid DefaultServeMux
and the corresponding helper functions. Use your own locally-scoped servemux instead, like we have been doing in this project so far.
Additional information
Servemux features and quirks
In Go’s servemux, longer URL patterns always take precedence over shorter ones. So, if a servemux contains multiple patterns which match a request, it will always dispatch the request to the handler corresponding to the longest pattern. This has the nice side-effect that you can register patterns in any order and it won’t change how the servemux behaves.
Request URL paths are automatically sanitized. If the request path contains any
.
or..
elements or repeated slashes, the user will automatically be redirected to an equivalent clean URL. For example, if a user makes a request to/foo/bar/..//baz
they will automatically be sent a301 Permanent Redirect
to/foo/baz
instead.If a subtree path has been registered and a request is received for that subtree path without a trailing slash, then the user will automatically be sent a
301 Permanent Redirect
to the subtree path with the slash added. For example, if you have registered the subtree path/foo/
, then any request to/foo
will be redirected to/foo/
.
Host name matching
It’s possible to include host names in your URL patterns. This can be useful when you want to redirect all HTTP requests to a canonical URL, or if your application is acting as the back end for multiple sites or services. For example:
mux := http.NewServeMux() mux.HandleFunc("foo.example.org/", fooHandler) mux.HandleFunc("bar.example.org/", barHandler) mux.HandleFunc("/baz", bazHandler)
When it comes to pattern matching, any host-specific patterns will be checked first and if there is a match the request will be dispatched to the corresponding handler. Only when there isn’t a host-specific match found will the non-host specific patterns also be checked.
What about RESTful routing?
It’s important to acknowledge that the routing functionality provided by Go’s servemux is pretty lightweight. It doesn’t support routing based on the request method, it doesn’t support clean URLs with variables in them, and it doesn’t support regexp-based patterns. If you have a background in using frameworks like Rails, Django or Laravel you might find this a bit restrictive… and surprising!
But don’t let that put you off. The reality is that Go’s servemux can still get you quite far, and for many applications is perfectly sufficient. For the times that you need more, there’s a huge choice of third-party routers that you can use instead of Go’s servemux. We’ll look at some of the popular options later in the book.