You're reading a sample of this book. Get the full version here.
Let's Go Foundations › Routing requests
Previous · Contents · Next
Chapter 2.3.

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:

File: main.go
package main

import (

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)

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:

File: main.go
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)

    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)

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

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("", fooHandler)
mux.HandleFunc("", 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.