Home About Projects Blog

Golang Http State Management

Thu, Sep 29, 2022

In golang, people often write http services that need access to a database, loggers, or some other shared state. Because there are so many ways to accomplish the goal of sharing state, it’s not always clear what is the best way to pass references to your http endpoint handlers. In this post, I’ll go over a few of them and try to discuss some pros and cons of each (if I think they exist). This isn’t a comprehensive list, but it’s a few of the most common ways I’ve seen or used in the past.

Global State

When you first start out, global state (especially for small projects) is a quick and dirty way to get things done. It’s a way that you can quickly prototype without having to have a lot of boilerplate code which can seem appealing when starting a new project. For example, if you have a database that you want to set up or some other shared state, you can just create a global variable and use it in your handlers without much thought. You can do something like this:

Code block 1

That’s it. It’s really not that complicated. That being said, I would strongly advise against using global state in your production code. It’s nice for a quick and dirty prototype, but it’s not a good long term solution for a long litany of reasons that will be discussed below.

Pros

Cons

Through Request Context

Another way to pass state to your handlers is through the request context. This isn’t widely used, but it avoids using global state and in doing so can avoid most of its shortcomings. Sharing state through the request context is a bit more complicated than using global state because it requires a little more setup. Although, it’s not so complicated that it’s not worth it. For this example, I’ll be using for the router gorilla/mux. Here’s what it would look like in code:

Code block 2

As you can see, it takes a little more setup than using global state, but I find that it’s worth it in order to avoid using global state. Basically, you create a context with the value you want to pass to your handlers using the BaseContext struct field on the http.Server struct. Then, you can access that value in your handlers using the Context() method on the http.Request struct.

Pros

Cons

Through Wrapper Closures

Given the shortcomings of the previous two methods, we’ll continue to explore our options; hopefully, we’ll find a better solution that gives us more flexibility and fewer headaches. The next method that we’ll explore is through wrapper closures. This method is about as complex as the previous method, but maintains type-safety for the state that you’re passing. This method is also a bit more flexible than the previous two methods because you can control the access that individual handlers have to the state. This is what it looks like:

Code block 3

Now that’s what I like to see! We have type safety, properly scoped variables, and we can control the access that individual handlers have to the state. This is definitely the best method that we’ve seen so far. It also comes with the added bonus of being able to run handler-level initialization code! This allows you to pull environment variables at startup per handler, validate input, initialize structs scoped only to the handler, or any other one-time task you may need to do. Below is an example of how you would do that.

Code block 4

These are nice because they limit the scope of anything done to the handler. Nothing else can access that scope. This can be incredibly useful for mapping custom request and response types for your handlers and makes it much easier to read and understand what’s going on.

Pros

Cons

Through a Server Object

The last method was pretty good, but maybe we can try to improve just a bit more. To do that, we’ll use a server object that shares all the state that we want to pass to our handlers. This method is a bit more complex than the all the previous methods. It would look something like this:

Code block 5

This is nice and all, but it loses a lot of the utility that we discovered from the last method. Luckily, we can combine the two methods to get the best of both worlds! It would look a little something like this:

Code block 6

There we go! Now we have all the flexibilty from the solution with closures, but we also have a very clean way of actually passing the desired state to out handlers.

Pros

Cons

My Preference (and why I use it)

So, now that we’ve explored all the different methods, let’s talk about which one I prefer and why. Honestly, it’s a pretty close call between the last two methods. It really is a matter of trade-offs and preferences. I personally prefer the third method. It gives me the most flexibility, it’s easy to test, provides maximum utility, and I value the readability and variable access it provides over the slightly shorter calling syntax of the fourth method. The fourth method is great, but it’s a bit too complex for my tastes. I like to keep things simple and I think that the third method is the simplest of the four methods while also providing all the safety and flexibility that I need when I write an application.

Conclusion

So, there you have it! We’ve explored four different methods of passing state to handlers in Go. We’ve seen the pros and cons of each method, and we’ve seen why you might choose one over the other in a given scenario. I hope that this has been helpful to you and that you’ve learned something new. Thanks for reading!

P.S. I would strongly recommend that you not use global variables to pass state to your handlers. It’s a bad practice, and it can lead to some very hard to debug problems. I personally wouldn’t touch that method with a ten-foot pole even in prototyping.