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.

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.