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.