« Back to home

Introducing Go-Json-Rest

Posted on

A quick and easy way to setup a RESTful JSON API in Go

A few weeks ago I wrote Go-UrlRouter, here is the next step, Go-Json-Rest, a very small REST framework built around it.

First, a few thoughts, kind of design principles for this project.

REST is everywhere. The full-featured web frameworks are moving from the server side to the client side. Django and Catalyst are dinosaurs, and are replaced by Dojo, Ember.js, backbone.js, JQueryMobile, … And these Javascript frameworks are usually talking to the server through a REST API. Same for the native mobile apps, most of them are just a sophisticated frontends talking to a server though this same kind of API.

REST does not just use HTTP, it embraces HTTP. The beauty of REST is that things like cache headers, if-modified-since, etag, redirections, … are implementable on a REST API. Some rest frameworks try to totally hide HTTP, by directly mapping the HTTP request to a procedure call. I think this should not be the case, and that the user of a REST framework should be able to use and have control of HTTP features.

Lightweight and schema free. The strength and the weakness of REST are its flexibility. This is a paper, an architectural style, not a spec. As opposed to SOAP or XML-RPC, there is no strict definition, no schema of a REST API. Some work have been done on this, I like SPORE for instance. But in my opinion this kind of REST schema definition is a distinct layer, and should be implemented on top of a thin framework that provides the generic REST capabilities, like URL routing, payload encoding and decoding, …

I only need JSON. REST does not define which representation to use, it can be JSON, XML, plain text, whatever. But really, JSON is the standard. I know it doesn’t play nice with hypermedia, and that in theory hypermedia is a required part of a RESTful API (see HATEOAS). In practice, nobody cares. I think It’s up to the user of the framework to define the convention about how to put links in the JSON payload if needed.

If you didn’t find that boring, then you can try to discuss where this project stands in the REST maturity model. But really I think it’s better to try it and get started:

First, Install the package

go get github.com/ant0ine/go-json-rest

Then, copy this simple example

package main
import (
    "github.com/ant0ine/go-json-rest"
    "net/http"
)

func main() {
    handler := rest.ResourceHandler{
                EnableRelaxedContentType: true,
        }
    handler.SetRoutes(
        rest.Route{"GET", "/countries", GetAllCountries},
        rest.Route{"POST", "/countries", PostCountry},
        rest.Route{"GET", "/countries/:code", GetCountry},
    )
    http.ListenAndServe(":8080", &handler)
}

type Country struct {
    Code string
    Name string
}

var store = map[string]*Country{}

func GetCountry(w *rest.ResponseWriter, r *rest.Request) {
    code := r.PathParam("code")
    country := store[code]
    if country == nil {
        rest.NotFound(w, r)
        return
    }
    w.WriteJson(&country)
}

func GetAllCountries(w *rest.ResponseWriter, r *rest.Request) {
    countries := make([]*Country, len(store))
    i := 0
    for _, country := range store {
            countries[i] = country
            i++
    }
    w.WriteJson(&countries)
}

func PostCountry(w *rest.ResponseWriter, r *rest.Request) {
    country := Country{}
    err := r.DecodeJsonPayload(&country)
    if err != nil {
        rest.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    if country.Code == "" {
        rest.Error(w, "country code required", 400)
        return
    }
    if country.Name == "" {
        rest.Error(w, "country name required", 400)
        return
    }
    store[country.Code] = &country
    w.WriteJson(&country)
}

Finally, test with Curl

curl -i -d '{"Code":"US","Name":"United States"}' http://127.0.0.1:8080/countries
curl -i -d '{"Code":"FR","Name":"France"}' http://127.0.0.1:8080/countries
curl -i http://127.0.0.1:8080/countries/US
curl -i http://127.0.0.1:8080/countries

For more informations:

Please, let me know what you think, and feel free to open issues on Github.