RESTful web service in Go using Goji

Although net/http provides all necessary abstractions to create web services out of the box, there are alternative packages providing additional features and allowing to minimize boilerplate code. Today we will have a look at how to implement a web service with Goji.

Example of Goji web service

Let's start with a working example. You can get if from GitHub.

package main

import (  
    "encoding/json"
    "fmt"
    "net/http"

    "goji.io"
    "goji.io/pat"
)

type book struct {  
    ISBN    string "json:isbn"
    Title   string "json:name"
    Authors string "json:author"
    Price   string "json:price"
}

var bookStore = []book{  
    book{
        ISBN:    "0321774639",
        Title:   "Programming in Go: Creating Applications for the 21st Century (Developer's Library)",
        Authors: "Mark Summerfield",
        Price:   "$34.57",
    },
    book{
        ISBN:    "0134190440",
        Title:   "The Go Programming Language",
        Authors: "Alan A. A. Donovan, Brian W. Kernighan",
        Price:   "$34.57",
    },
}

func main() {  
    mux := goji.NewMux()
    mux.HandleFunc(pat.Get("/books"), allBooks)
    mux.HandleFunc(pat.Get("/books/:isbn"), bookByISBN)
    mux.Use(logging)
    http.ListenAndServe("localhost:8080", mux)
}

func allBooks(w http.ResponseWriter, r *http.Request) {  
    jsonOut, _ := json.Marshal(bookStore)
    fmt.Fprintf(w, string(jsonOut))
}

func bookByISBN(w http.ResponseWriter, r *http.Request) {  
    isbn := pat.Param(r, "isbn")
    for _, b := range bookStore {
        if b.ISBN == isbn {
            jsonOut, _ := json.Marshal(b)
            fmt.Fprintf(w, string(jsonOut))
            return
        }
    }
    w.WriteHeader(http.StatusNotFound)
}

func logging(h http.Handler) http.Handler {  
    fn := func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("Received request: %v\n", r.URL)
        h.ServeHTTP(w, r)
    }
    return http.HandlerFunc(fn)
}

If we run the code it will start server listening on "localhost:8080". The server will handle two paths and return json with all books for /books request and json with a book with specified ISBN for /books/{isbn} one.

For example, http://localhost:8080/books/0321774639 will return:

{"ISBN":"0321774639","Title":"Programming in Go: Creating Applications for the 21st Century (Developer's Library)","Authors":"Mark Summerfield","Price":"$34.57"}

Additionally the service logs all requested URLs to standard output. For the previous example, it will log:

Received request: /books/0321774639  

Analyzing code

Goji API looks very similar to net/http, while it adds some useful features including middleware and more convenient parameter parsing.

Handlers

Registering handlers in Goji is very similar to net/http:

mux := goji.NewMux()  
mux.HandleFunc(pat.Get("/books"), allBooks)  
mux.HandleFunc(pat.Get("/books/:isbn"), bookByISBN)  
...
http.ListenAndServe("localhost:8080", mux)  

We create mux, add handlers with specified paths and run server passing host:port and mux as parameters. Please note a convenient way to define a path variable:

pat.Get("/books/:isbn")  

which can be consumed later from Request:

isbn := pat.Param(r, "isbn")  
Middleware

If you never used middleware before, it can be conceived as a wrapper around handler. Middleware allows to execute any code before, after or even instead of a handler. In our example there is only one logging middleware, which prints requested URL and relays to handler for further processing. Registering middleware is as simple as:

mux.Use(logging)  

Production web services usually register multiple middlewares, i.e. authentication/authorization, logging, metrics.

Bottom line

Goji could be a good choice for developing RESTful web services. It plays nice with net/http from standard library while bringing to the table middleware and more convenient parsing of path parameters.

comments powered by Disqus