Build a web application in Golang 🕸
Go is getting more and more popular as the go-to language to build web applications.
This is in no small part due to its speed and application performance, as well as its portability. There are many resources on the internet to teach you how to build end to end web applications in Go, but for the most part they are either scattered in the form of isolated blog posts, or get into too much detail in the form of books.
With this tutorial, I hope to find the middle ground and provide a single resource which describes how to make a full stack web application in Go, along with sufficient test cases.
The only prerequisite for this tutorial is a beginner level understanding of the Go programming language.
“Full Stack” ?
We are going to build a community encyclopedia of birds. This website will :
- Display the different entries submitted by the community, with the name and details of the bird they found.
- Allow anyone to post a new entry about a bird that they saw.
This application will require three components :
- The web server
- The front-end (client side) app
- The database
Setting up your environment
This section describes how to set up your environment and project structure for the first time. If you have built another project in go, or know the standard directory structure, you can skip this section and go to the next one
1. Set up your $GOPATH
Run this command to check the current value of your
$GOPATH environment variable :
If you do not see a directory name, add the
GOPATH variable to your environment (you can select any directory location you want, but it would be better if you create a new directory for this) :
You can paste the above line in you
.zshrc file, in case you wish to make the variable permanent.
2. Set up your directory structure
Hereafter, the “Go directory” will refer to the location described by your
$GOPATH environment variable. Inside the Go directory, you will have to create 3 folders (if they are not there already) :
# Inside the Go directory
The purpose of each directory can be seen from its name:
bin- is where all the executable binaries created by compiling your code go
pkg- Contains package objects made by libraries (which you don't have to worry about now)
src- is where all your Go source code goes. Yes, all of it. Even that weird side project that you are thinking of making.
3. Creating your project directory
The project folders inside the
src directory should follow that same location structure as the place where your remote repository lies. So, for example, if I want to make a new project called "birdpedia", and I make a repository for that under my name on github, such that the location of my project repository would be on "github.com/sohamkamani/birdpedia", then the location of this project on my computer would be :
Go ahead and make a similar directory for your project. If you haven’t made an online repo yet, just name the directories according to the location that you plan to put your code in.
This location on your computer will henceforth be referred to as your “project directory”
Starting an HTTP server
Inside your project directory, create a file called
main.go inside your project directory :
This file will contain the code to start your server :
fmt.Fprintf, unlike the other "printf" statements you may know, takes a "writer" as its first argument. The second argument is the data that is piped into this writer. The output therefore appears according to where the writer moves it. In our case the ResponseWriter
wwrites the output as the response to the users request.
You can now run this file :
go run main.go
And navigate to http://localhost:8080 in your browser, or by running the command :
And see the output: “Hello World!”
You have now successfully started an HTTP server in Go.
Our server is now running, but, you might notice that we get the same “Hello World!” response regardless of the route we hit, or the HTTP method that we use. To see this yourself, run the following
curl commands, and observe the response that the server gives you :
curl -X POST localhost:8080
curl -X PUT localhost:8080/samething
All three commands still give you “Hello World!”
We would like to give our server a little more intelligence than this, so that we can handle a variety of paths and methods. This is where routing comes into play.
Although you can achieve this with Go’s
net/http standard library, there are other libraries out there that provide a more idiomatic and declarative way to handle http routing.
Installing external libraries
We will be installing a few external libraries through this tutorial, where the standard libraries don’t provide the features that we want.
When we install libraries, we need a way to ensure that other people who work on our code also have the same version of the library that we do.
In order to do this, we use a “package manager” tool. This tool serves a few purposes:
- It makes sure the versions of any external libraries we install are locked down, so that breaking changes in any of the libraries do not affect our code.
- It fetches the required external libraries and stores them locally, so that different projects can use different versions of the same library, if they need to.
- It stores the names and versions of all our external libraries, so that others can install the same versions that we are working with on our system.
The official package manager for Go (or rather “official experiment” that is “safe for production use” as described on its homepage) is called
dep. You can install dep by following the setup guide. You can verify its installation by running :
which should output some information on the version if successful.
To initialize package management for our project, run the command :
THis will create the
Gopkg.lock files, which are the files that are used to record and lock dependencies in our project.
Next, we install our routing library:
dep ensure -add github.com/gorilla/mux
This will add the
gorilla/mux library to your project.
Now, we can modify our code to make use of the functionality that this library provides :
Testing is an essential part of making any application “production quality”. It ensures that our application works the way that we expect it to.
Lets start by testing our handler. Create a file called
Go uses a convention to ascertains a test file when it has the pattern
To run this test, just execute:
go test ./...
from your project root directory. You should see a mild message telling you that everything ran ok.
Making our routing testable
If you notice in our previous snippet, we left the “route” blank while creating our mock request using
http.newRequest. How does this test still pass if the handler is defined only for "GET /handler" route?
Well, turns out that this test was only testing our handler and not the routing to our handler. In simpler terms, this means that the above test ensures that the request coming in will get served correctly provided that it’s delivered to the correct handler.
In this section, we will test this routing, so that we can be sure that each handler is mapped to the correct HTTP route.
Before we go on to test our routing, it’s necessary to make sure that our code can be tested for this. Modify the
main.go file to look like this:
Once we’ve separated our route constructor function, let’s test our routing:
Now we know that every time we hit the
GET /hello route, we get a response of hello world. If we hit any other route, it should respond with a 404. In fact, let's write a test for precisely this requirement :
Now that we’ve learned how to create a simple http server, we can serve static files from it for our users to interact with.
Serving static files
There are 3 steps we need to take in order to make our server serve these static assets.
- Create static assets
- Modify our router to serve static assets
- Add tests to verify that our new server can serve static files
Create static assets
To create static assets, create a directory in your project root directory, and name it
Next, create an HTML file inside this directory. This is the file we are going to serve, along with any other file that goes inside the
assets directory :
Modify the router
Interestingly enough, the entire file server can be enabled in just adding 3 lines of code in the router. The new router constructor will look like this :
Testing the static file server
You cannot truly say that you have completed a feature until you have tests for it. We can test the static file server by adding another test function to
To actually test your work, run the server :
go run main.go
And navigate to http://localhost:8080/assets/ in your browser.
Making a simple browser app
Since we need to create our bird encyclopedia, lets create a simple HTML document that displays the list of birds, and fetches the list from an API on page load, and also provides a form to update the list of birds :
Adding the bird REST API handlers
As we can see, we will need to implement two APIs in order for this application to work:
GET /bird- that will fetch the list of all birds currently in the system
POST /bird- that will add an entry to our existing list of birds
For this, we will write the corresponding handlers.
Create a new file called
bird_handlers.go, adjacent to the
First, we will add the definition of the
Bird struct and initialize a common
Next, define the handler to get all birds :
Next, the handler to create a new entry of birds :
The last step, is to add these handler to our router, in order to enable them to be used by our application :
The tests for both these handlers and the routing involved are similar to the previous tests we wrote for the
GET /hello handler and static file server, and are left as an exercise for the reader.
If you’re lazy, you can still see the tests in the source code
Adding a database
So far, we have added persistence to our application, with the information about different birds getting stored and retrieved.
However, this persistence is short lived, since it is in memory. This means that anytime you restart your application, all the data gets erased. In order to add true persistence, we will need to add a database to our stack.
Until now, our code was easy to reason about and test, since it was a standalone application. Adding a database will add another layer of communication.
You can read about how to integrate a postgres database into your Go application in my next post
You can find the source code for this post here