Web Handlers and Middleware in GoLang

This is a collection of approaches to write web handlers and middleware in Go. It’d be useful for those who know the basics of writing web services in Go and are now looking at more modular, cleaner, and a little more advanced coding techniques.

The full code for this is here: https://play.golang.org/p/Y2_3P0aBP4I. We will consider it piece by piece and incrementally. I’ve named the handler functions with an ‘h’ prefix. The now() function prints the current time.

A Typical, Simple Handler

We start with simple, typical handler functions as shown below.

Output

In functions h1 and h2, we have a simple logging functionality which prints the time at which the execution entered the function and then exited it. Here, we are having to repeat the logging code in each function, which isn’t convenient.

Adding Middleware

Middleware allows you to write code that will always be executed for handlers, which allows for better modularization.

The function h3 (above) is much simpler and only has its core functionality. The logging functionality is moved into a separate wrapper function, logger. Since HandleFunc requires the second parameter to have a particular signature (w http.ResponseWriter, r *http.Request), our wrapping function returns a function with the same signature within which the original function is called. Henceforth, all functions that require the entry and exit time logging can be handled easily by reusing this middleware.

The output will be similar to when we called h1 and h2.

type HandlerFunc

In the previous listing, the function signature func logger(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) feels lengthy. To make it easier, there is a type definition in the net/http package.

Therefore you can simplify the function signature as below, which makes the code cleaner. The output will be the same.

ServeHTTP

We saw previously a type redefinition called HandlerFunc in net/http. This type has an attached method called ServeHTTP.

As you can see, ServeHTTP simply calls the original handler function. Hence, in our code, it is almost equivalent to substitute the function call f(w, r) with h.ServeHTTP(w, r). We can therefore rewrite the code as shown below and it would still work similarly and give you similar output. (There is an extra step in the call stack though.)

Personally, I have not taken to using ServeHTTP in this context as much. I don’t know if there is any particular advantage either. But I do see this code approach often, and therefore it is good to at least know what it means.

Authorization/Authentication and Code Forwarding Decisions

Given that we have control over the actual core function in our wrapper, we can now make decisions on whether to forward the call to the wrapped function or not. In the code below, we have added an authorization wrapper too.

In this very simple example, we’re merely checking whether the method is a GET method. If it is, we allow the wrapped method to be called. If not, we reject the request with a 404. It is usually here that we’d check for the session id and see if this particular request has permissions to call the wrapped function.

Output:

Passing Parameters

The handler functions we have used in functions h1 and h2 have a strict signature that accept a ResponseWriter and a *Request. We cannot send any other parameters to it. With wrapped functions we have control over the parameters passed in to our middleware. We can still, however, only return a function that matches the original handler signature.

In the code above, we are passing in a parameter to our param middleware. Accessing the end point via curl gives me the below output.

Keeping Handler Resources Encapsulated

Usually as part of handling web requests, we need to access external resources like databases, config settings, etc. In our early simple implementations, we tend to keep this data global within the main package. There is a better way to do this.

In the code below, we are taking a dummy example where our environment and db connection is just a string (ideally we would have an actual sql.DB instance). Instead of having them as separate variables, we keep them together in a struct called config.

Our function h8() is defined on our config type. Remember that the core handler function has to have the same signature as we expect for all handler functions, but it can be attached to any type.

config.h8() returns a function that matches the required signature and therefore this is valid. Interestingly, our core handler function now has access to all the common data like the db connection and the environment settings because it is a method on the config type.

Output:

One Time Initializations

Wrapper functions are invoked when you pass them in http.HandleFunc() calls. This invocation is done only once. The function that it returns, though, is called each time the end point is hit by client requests. This means that we can do any one time initializations as part of our middleware function.

In the example above, we have set the time that the middleware was first invoked. Since h9() is a closure over the actual handler function, it will have access to the variables set in the scope of h9().

Output:

Adding Multiple Routes and Middleware

As programs grow, there will be many endpoints added. It becomes difficult to ensure that everybody sets up the middleware calls correctly. In projects that I worked on, I was also not a big fan of a huge build up of route definitions in main(). To get around these issues, I had a single place where I would add all my route handlers with the necessary middleware.

The code here is a simplified version of that idea: https://play.golang.org/p/S0cwVCpAnij

Edit: I wrote this post based on Mat Ryer’s posts and some of my own thoughts. I then see that Mat spoke about this at Gophercon EU 2019. The video is here: https://www.youtube.com/watch?v=8TLiGHJTlig. There are a couple of additional points I’d like to add from that — one that was new to me and one, again, where I’d come to my own variation of it previously.

WriteResponse

Writing an http response back is a common task. I found value in abstracting that into a single method. Mat in his video has shown a much smaller function that takes care of only JSON responses, but I used to have something similar to the one below where I could handle different response types.

Request and Response Data Types

This one was new and interesting for me and I intend to adopt it. One of the issues I saw was a proliferation of different types that are accepted in the request and sent in the response. Keeping it within the function means that we can reuse the same name in another handler function without having to invent unique names. Types that work with a particular handler are together and it also increases maintainability.

I’m on LinkedIn and Twitter. I help people and companies adopt and build solutions with GoLang, GCP, Angular, and Ionic. I conduct training on GoLang and am also a Google Cloud Authorized Trainer with all the current GCP technical certifications. I am looking to do more on Google Cloud across all their services.

tech architect, tutor, investor | GCP 12x certified | youtube/AwesomeGCP | Google Developer Expert | Go