Do you need a web framework for Go?

Tushar Soni
5 min readDec 27, 2018

--

Go’s stdlib makes a strong case for not using a web framework. We may need to give this a second thought.

The question of whether you need a web framework comes up more often in Go than in any other language. Would you create a web application in Ruby without Ruby on Rails? Python without Django? PHP without Laravel? In Go, however, it is possible to create a complex web application with just the stdlib. It has everything you need including managing the http request / response lifecycle, setting up http servers, marshaling / unmarshaling JSON, and more.

Let’s take a look at what is needed today to create a simple backend API that could be consumed by an iOS/Android app, frontend SPA, or third parties. Then, we’ll use the stdlib to come up with the easiest solution to these problems. Spoiler alert: we may run into a few bumps.

Routing

This is a must. Any non-trivial application can have tens or hundreds of routes. You need a scalable way of managing these routes. You need to be able to use the conventions of a REST application such has embedding variables in the URL easily and pattern matching them to a controller. So, how hard is it to match /item/{id} to a controller in Go?

A quick Google search brings up a StackOverflow answer. The top-rated answer says “it’s not too hard to create your own handler that can use regular expressions or any other patterns”. It goes on to give a 25 line compiled (but not tested) solution to the problem. Easy enough. Copy paste and you’re done. Or are you? You realize your needs may be a little bit broader than that and you go back to Google looking for a more ‘complete’ solution. You stumble upon a popular Go library called Gorilla Mux. Again, easy enough. Add a small dependency and you’re good to go.

While it’s a perfectly valid solution, you have now admitted that even though stdlib is powerful, it might be a little too raw. It needs a layer every now and then to make it easy enough to be used.

Side note: IMHO, using Gorilla Mux is an excellent choice for many where it strikes a really good balance in not adopting a super-heavy framework and gaining more flexibility.

Once you have your routing figured out, you’ll need to start reading and writing requests. For the purpose of this article, we’ll assume you’re only dealing with JSON.

Writing JSON Responses

Go’s stdlib provides excellent JSON support with encoding/json package. Let’s say you have a user object, and you want to write a 200 OK response with the user object as JSON.

Yikes, do you want to put that into production? I know you jumped out of your chair, spilled your morning coffee, and screamed “No!”. Or, maybe not. Anyhow, let’s make it a little better:

If I curl the endpoint with that handler and inspect the headers, I get:

Content-Type: text/plain; charset=utf-8

Nope, don’t want that. Back to the code!

Not the best piece of code but it does the job at this point. Remember you’re building an app with tens or maybe hundreds of routes? Clearly, this code is going to get repeated a lot. Let’s put it in a function because you don’t want to repeat yourself.

Sigh, that was …easy? Sure.

Reading JSON Body

Just like marshaling JSON in Go is supported with the encode/json package, so is unmarshaling. But, there’s obviously more to it. One of the Ten Commandments of web development is “Don’t trust thy users’ input” (I might have made that up, but it’s important). One of the aspects of not trusting the input is validation. You want to make sure that the input you’re getting is within the bounds of what your application can handle. Since I’m in no mood to write package that can do all sorts of validation (who remembers the regex for validating emails anyway?), I do a quick Google search and find GoValidator.

The following code, reads from the request body, unmarshals it into a struct, and validates it using GoValidator. At any point, if it fails, we send back a 400 Bad Request status.

You know the drill — put it in a function and reuse it for all your routes. Smart. Moving along..

At this point, you have written a few functions to ease the development of the application. If you’re being nice to your functions, you may even put them in their own packages, add some tests, and some documentation. The next time you write a new application, you could reuse this code! If you’re feeling generous, you could just open-source it for others to use. Now, every one could go through this dance or they could just use your solution.

Do you think your packages qualify as a web framework? I do not. It’s more of a toolkit to help with the development of web applications in Go. There’s one thing that’s missing though: putting everything together — the ‘glue code’. It can establish patterns (hopefully good ones) that can make your code more modular, reusable, and testable.

Before we get to that, let’s take a look at one more important aspect of Go apps.

Database … and app setup

It’s almost 2019 and SQL has made its comeback. Assuming it’s your choice, you can leverage Go’s database/sql package to query your database. It just needs a little bit of boilerplate:

  • Read your configuration file
  • Open the database connection
  • Close the database connection when the application exits

It’s easy to do the above. But not trivial. Even if you use a popular Go ORM such as GORM, you still need to synchronize the lifecycle of the database connection with your app’s lifecycle. Moreover, if you want certain features like per-request transactions, you would need to add that manually. I’m not doubting your superior programming skills and have full faith that you can achieve this flawlessly, but people such as myself tend to forget these bits of code and want something reusable. Especially, since I would want this in every app I develop.

Reading configuration and setting up database connection falls under the ‘set up’ phase of the application. You would want to do this before starting your HTTP server. The main function seems reasonable. Let’s take a look at what that main function could grow into:

Globals, no graceful shutdown, bloated main function.. You do not want that. You want this:

Trust me that I did not just hide everything behind the Run method. These are both applications that I’ve written and I’m happy with just one of them (your guess).

Given my love for Go, I wouldn’t just leave you with just problems and no solution. I have a solution, I promise! It brings you much closer to the second application in the previous section.

My proposed solution is a lightweight dependency injection based web-framework that modularizes your app. You can get a sneak peek of Copper here. In Part 2, I’ll do a proper introduction of Copper and how it solves some of the problems we talked about.

If you enjoyed this and are interested in Part 2, please ‘clap’ and I’ll be sure to do a quick follow-up! If you have your own solution, comment below and let me know.

--

--