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.

 1package main
 2
 3import (
 4    "fmt"
 5    "net/http"
 6    "log"
 7    "github.com/julienschmidt/httprouter"
 8)
 9
10func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
11    fmt.Fprint(w, "Welcome!\n")
12}
13
14func main() {
15    router := httprouter.New()
16    router.GET("/", Index)
17    log.Fatal(http.ListenAndServe(":8080", router))
18}

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.

1type Book struct {
2    ISDN   string `json:"isdn"`
3    Title  string `json:"title"`
4    Author string `json:"author"`
5    Pages  int    `json:"pages"`
6}

Our main.go file now looks like:

1func main() {
2    router := httprouter.New()
3    router.GET("/", Index)
4    router.GET("/books", BookIndex)
5    router.GET("/books/:isdn", BookShow)
6    log.Fatal(http.ListenAndServe(":8080", router))
7}

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

 1{
 2  "meta": null,
 3  "data": [
 4    {
 5      "isdn": "123",
 6      "title": "Silence of the Lambs",
 7      "author": "Thomas Harris",
 8      "pages": 367
 9    },
10    {
11      "isdn": "124",
12      "title": "To Kill a Mocking Bird",
13      "author": "Harper Lee",
14      "pages": 320
15    }
16  ]
17}

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:

 1func TestIndex(t *testing.T) {
 2    router := httprouter.New()
 3    router.GET("/", Index)
 4
 5    req, _ := http.NewRequest("GET", "/", nil)
 6    rr := httptest.NewRecorder()
 7    router.ServeHTTP(rr, req)
 8
 9    if status := rr.Code; status != http.StatusOK {
10        t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
11    }
12}

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.