Create a Go API in 5 minutes Part 1

People have strong opinions about Go, both positive and negative. However, like any tool it’s about choosing the right one for the job. One of the areas in which Go shines is how easy it makes it to create an API. In this article we are going to write up a barebones example which might be followed up by later parts expanding upon what we built.


We’ll start off the way any Go program starts.

package main

Now lets define the structs that we’ll be using to represent a mock database. Everyone loves dogs and every tutorial I read seems to use them so lets go with that. Below we have a Dog struct with a Name, Type, and Age. Below that is a dogs variable that defines a list of Dog structs. If you’re wondering, “what are those weird json things?”, they’re useful for when we want to convert json into our Dog struct later on.

...
type Dog struct {
Name string `json:"name"`
Type string `json:"type"`
Age int `json:"age"`
}
var dogs []Dog

Because Go is a modern language and was built with the web and distributed programming in mind, it has most of the necessary tools to build an API baked into the standard library. However, in order to set router method types and more easily parse requests we will add the mux package.

...
import "fmt"
import "net/http"
import "github.com/gorilla/mux"
...

If you’re new to Go and wondering why “github.com” is in a package name, it’s because Go requires absolute references to outside imports. I definitely recommend reading up on why and how this works because it can cause a lot of headache down the road.

Now lets jump into the main body of the service.

...
func main() {
// init server
router := mux.NewRouter()
// create routes
router.HandleFunc("/dogs", HandleGetDogs).Methods("GET")
router.HandleFunc("/dogs/{name}", HandleGetDog).Methods("GET")
router.HandleFunc("/dogs", HandleCreateDog).Methods("POST")
router.HandleFunc("/dogs/{name}",HandleDeleteDog).Methods("DELETE")
 fmt.Println("running server on 8000...")
fmt.Println(http.ListenAndServe(":8000", router))
}

At this point we initialized our router and have created four API endpoints that allow us to get all of the dog objects, get a single dog, create a dog, and remove a dog.

But wait! Where are the “Handle” functions coming from? Let’s get to that down below.

The handler below returns the list of dogs encoded in json. We will have to add another import for this to work.

...
import "encoding/json"
...
func HandleGetDogs(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(dogs)
}

Next, lets allow a user to specify the dog they want to return. It first grabs the list of parameters from the user request and then searches the list for the specified dog.

func HandleGetDog(w http.ResponseWriter,  r *http.Request) {
paramaters := mux.Vars(r)

for i, dog := range dogs {
if paramaters["name"] == dog.Name {
json.NewEncoder(w).Encode(dogs[i])
return
}
}
json.NewEncoder(w).Encode("not found")
}

To create a dog we first extract the request json body as bytes before marshaling it into a Dog struct. This is then added to our mock database. To read the body we need another import.

...
import "io/ioutil"
...
func HandleCreateDog(w http.ResponseWriter, r *http.Request) {
bytes, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println("failed to marshal body")
}
dog := Dog{}
json.Unmarshal(bytes, &dog)
dogs = append(dogs, dog)
w.WriteHeader(http.StatusOK) // reply with a 200 OK
}

Handling deletions is very similar to what we’ve done up until this point.

func HandleDeleteDog(w http.ResponseWriter, r *http.Request) {
paramaters := mux.Vars(r)
for i, dog := range dogs {
if paramaters["name"] == dog.Name {
dogs = append(dogs[:i], dogs[i+1:]...) // removes item at i
}
}
w.WriteHeader(http.StatusOK) // reply with a 200 OK
}

If you’ve gotten to the end thanks for reading. If you found it easy and are interested in more challenging API extensions or topics comment below and I might add addition parts.