Building a Hello World API in Go (StudentLayer Part 2)

CJ Williams
3 min readAug 21, 2021

In this post, we will set up our development environment and create a basic, Hello World style, service. While we will go through how we set up our project, this post won’t be a step-by-step tutorial — there are many superior tutorials for all of the topics we will cover.

Side note: From herein, I will only reference commands for a Unix-based system (macOS specifically), though these should work for any POSIX-compliant OS.

The code for this post can be found here

Installing Go

For the time being, we will use the latest stable version of Go — 1.16.6. As the project goes on we will attempt to stay up to date, dealing with any dependency maintenance issues that may arise — a potentially painful but very relevant endeavour.

To speed things up, we’ll just download the macOS installer from the official Golang site, but feel free to download and install the archive if, for instance, you are running Ubuntu.

Verify you have 1.16.6 by running:

go version

If you have had previous installations, you may need to uninstall any previous versions. Problems can also arise if your GOPATH and GOROOT are not set correctly.

Go Modules

We will use Go Modules to manage dependencies. We will start by creating a module for our project, with a name that matches our GitHub repository. We’ll run this within our Git repository:

go mod init github.com/WilliamsCJ/studentlayer

Creating a basic router

Now we can get down to creating a basic HTTP request router that will form the basis of our service. For this, we will use net/http from the Go standard library. Whilst our service is in its infancy, we don’t need a complexity web framework or router, such as Gin or Chi. net/http serves our needs and allows us to really understand what is going on.

We can create a basic Hello World router as follows:

package mainimport(
"fmt"
"log"
"net/http"
)
func main() {
router := http.NewServeMux()
router.HandleFunc("/studentlayer", func(w http.ResponseWriter, r *http.Request) {
log.Printf("Serving %s request for %s", r.Method, r.URL)
_, err := fmt.Fprintf(w, "Hello world")
if err != nil {
panic(err)
}
})
log.Println("Running on localhost:1000...")
log.Fatal(http.ListenAndServe(":1000", router))
}

This program creates a router that handles a single route /studentlayer. As we only have one (small) handler function, we can get away with using an anonymous function. However, as things get more complicated, we will want to define separate handler functions. Currently, all our handler does is log some request information received from the http.Requeststruct and writes our Hello World response to the http.ResponseWriter using fmt.Fprintf.

Note however that we’re using log and not fmt for logging output. This is for the following reasons:

  1. log is concurrency safe (useful for later). fmt is not.
  2. log outputs to stderr. fmt outputs to stdout. Read this if you don’t understand why this matters. N.B. we would consider logging to be ‘diagnostic output’. The ‘normal output’ of our program is our API responses.
  3. log adds timestamps automatically.

Running our router

We can start serving requests from the router by running:

go run main.go

This will give us an output like so:

2021/07/24 17:06:45 Running on localhost:8080...
2021/07/24 18:27:49 Serving GET request for /studentlayer

We can also compile our code into a binary that we can subsequently run. This is done as such:

go build -o studentlayer
./studentlayer

Note: The -o flag is not strictly necessary in this instance as our binary will automatically be named studentlayerbecause our module name (github.com/WilliamsCJ/studentlayer) ends with studentlayer.

Wrapping up

While our router doesn’t do much yet, it will form the basis of our service. In the next post, we will look at Docker-ising and deploying our service.

--

--

CJ Williams

Computer Science student @ University of St Andrews