Let's Go Middleware › How middleware works
Previous · Contents · Next
Chapter 6.1.

How middleware works

Earlier in the book I said something that I’d like to expand on in this chapter:

“You can think of a Go web application as a chain of ServeHTTP() methods being called one after another.”

Currently, in our application, when our server receives a new HTTP request it calls the servemux’s ServeHTTP() method. This looks up the relevant handler based on the request method and URL path, and in turn calls that handler’s ServeHTTP() method.

The basic idea of middleware is to insert another handler into this chain. The middleware handler executes some logic, like logging a request, and then calls the ServeHTTP() method of the next handler in the chain.

In fact, we’re actually already using some middleware in our application — the http.StripPrefix() function from serving static files, which removes a specific prefix from the request’s URL path before passing the request on to the file server.

The pattern

The standard pattern for creating your own middleware looks like this:

func myMiddleware(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        // TODO: Execute our middleware logic here...
        next.ServeHTTP(w, r)
    }

    return http.HandlerFunc(fn)
}

The code itself is pretty succinct, but there’s quite a lot in it to get your head around.

If this feels confusing, you can think of it more simply: myMiddleware() is a function that accepts the next handler in a chain as a parameter. It returns a handler which executes some logic and then calls the next handler.

Simplifying the middleware

A tweak to this pattern is to use an anonymous function inside myMiddleware() middleware, like so:

func myMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // TODO: Execute our middleware logic here...
        next.ServeHTTP(w, r)
    })
}

This pattern is very common in the wild, and the one that you’ll probably see most often if you’re reading the source code of other applications or third-party packages.

Positioning the middleware

It’s important to explain that where you position the middleware in the chain of handlers will affect the behavior of your application.

If you position your middleware before the servemux in the chain then it will act on every request that your application receives.

myMiddleware → servemux → application handler

A good example of where this would be useful is middleware to log requests — as that’s typically something you would want to do for all requests.

Alternatively, you can position the middleware after the servemux in the chain — by wrapping a specific application handler. This would cause your middleware to only be executed for a specific route.

servemux → myMiddleware → application handler

An example of this would be something like authorization middleware, which you may only want to run on specific routes.

We’ll demonstrate how to do both of these things in practice as we progress through the book.