I turned into a Gopher about 10 months ago and have never looked back. Like many other gophers, I quickly found the simple features of the language quite useful to quickly build fast and scalable software. When I was initially picking up Go, I was playing around with different multiplexers available to be used as an API server.

If you are coming from a Rails background like me, you probably would also have struggled in building all the features which one can get from Web Frameworks. Coming back to the multiplexers, I found 3 to be quite useful: Gorilla mux, httprouter and bone (ordered in ascending order of their performance). Even though bone had the best performance and a simpler handler signature, it was not mature enough to be used in a production environment yet. So I ended up using httprouter.

In this tutorial, I would build a simple REST API server with httprouter. In case you feel lazy and just want the code, you can directly check my GitHub repository.

Let us begin.

First: Create a Basic Endpoint#

Index is a handler function and needs to have three input parameters. This handler is then registered to the path GET / in the main function.

package main

import (
    "fmt"
    "net/http"
    "log"
    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    log.Fatal(http.ListenAndServe(":8080", router))
}

Now compile and run your program and go to http://localhost:8080 to see your API server in action.

Adding More Complexity#

We now have an entity called Book which can be uniquely identified with the field ISDN. Let us create a couple more actions: GET /books and GET /books/:isdn representing the Index and Show actions respectively.

type Book struct {
    ISDN   string `json:"isdn"`
    Title  string `json:"title"`
    Author string `json:"author"`
    Pages  int    `json:"pages"`
}

Our main.go file now looks like:

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/books", BookIndex)
    router.GET("/books/:isdn", BookShow)
    log.Fatal(http.ListenAndServe(":8080", router))
}

Now if you try to request GET https://localhost:8080/books, you get the following response:

{
  "meta": null,
  "data": [
    {
      "isdn": "123",
      "title": "Silence of the Lambs",
      "author": "Thomas Harris",
      "pages": 367
    },
    {
      "isdn": "124",
      "title": "To Kill a Mocking Bird",
      "author": "Harper Lee",
      "pages": 320
    }
  ]
}

These were the two entries of books which we hardcoded into the main function.

Refactoring the Code#

So far we have all the code in just one file, main.go. Let us move them to separate files. We now have a directory structure:

.
├── handlers.go
├── main.go
├── models.go
└── responses.go
  • Move all the JSON response related structs to responses.go
  • Move the handler functions to handlers.go
  • Move the Book struct to models.go

Writing Tests#

In Go, the *_test.go files are for tests. Let us create a handlers_test.go. We use the httptest package’s Recorder to mock the handlers:

func TestIndex(t *testing.T) {
    router := httprouter.New()
    router.GET("/", Index)

    req, _ := http.NewRequest("GET", "/", nil)
    rr := httptest.NewRecorder()
    router.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
    }
}

Refactoring Further: DRY Handlers, Logging and Routes#

We still define all the routes in the main function, our handlers look verbose, we lack log messages in the terminal, and we still need a BookCreate handler.

First, let us DRY out handlers.go. I created two helper functions:

  • writeOKResponse — writes responses with StatusOK and a model or slice of models
  • writeErrorResponse — writes a JSON error as a response for expected or unexpected errors

I also added populateModelFromHandler which unmarshals the body contents into any model you want. This is used in the BookCreate handler to populate a Book.

For logging, we create a Logger function that wraps around the handler functions and prints log messages before and after execution.

For routes, define all routes in one place in routes.go, and make a NewRouter function in router.go which reads all the routes and returns a usable httprouter.Router, wrapping each handler with the Logger function.

Your final directory structure should look like:

.
├── handlers.go
├── handlers_test.go
├── logger.go
├── main.go
├── models.go
├── responses.go
├── router.go
└── routes.go

For a larger project, you could organise into packages:

.
├── LICENSE
├── README.md
├── handlers
│   ├── books_test.go
│   └── books.go
├── models
│   └── book.go
├── store
│   └── *
├── lib
│   └── *
├── main.go
├── router.go
├── routes.go
└── logger.go

You can also put handlers, models, and all route functionalities under another package called app if you have a big monolithic server. Just keep in mind, Go is not like Java or Scala and there cannot be cyclic package calls — so take extra care with your package structure.

That is all and I hope this tutorial was useful. Cheers!


Originally published on Medium.