Look folks, I made a thing!
It’s called gimlet, and it’s a Go(lang) tool for making JSON/HTTP APIs (i.e. REST with JSON). Give it a whirl!
It’s actually even less a tool, and more of a toolkit or just “a place to put all of the annoying infrastructure that you’ll inevitably need when you want to build an JSON/HTTP interface, but that have nothing to do what whatever your API/application does: routing, serializing and serializing JSON.
Nothing hard, nothing terribly interesting, and certainly not anything you couldn’t do another way, but, it’s almost certainly true that this layer of application infrastructure is totally orthogonal to whatever you application is actually doing, so you should focus on that, and probaly use something like Gimliet.
Background
I’m using the term HTTP/JSON APIs for services where you send and recive JSON data over HTTP. Sometimes people call these REST APIs, and that’s not inaccurate, but I think REST is a bit more complicated, and not exactly the core paradigm that I’m pursuing with Gimlet.
Sending and reviving JSON over HTTP makes a lot of sense: there are great tools for parsing JSON and HTTP is a decent high level protocol for interprocess communication between simple data applications. Look up “microservices” at your leisure.
Go is a great language for this it has a lot of tooling that anticipates these kinds of applications, and the deployment model is really friendly to operations teams and systems. Also the static
typing and reasonable separation of private and public interfaces is particularly lovely.
So it should be no surprise that there are a lot tools for building stweb applications, frameworks even. They’re great, things like gorilla and negroni are great and provide a very useful set of tools for building Go web apps. Indeed even Gimlet uses components of each of these tools.
The issue, and reason for Gimlet, is that all of these tools assume that you’re building a web application, with web pages, static resources, form handling, session state handling, and other things that are totally irrelevant to writing JSON/HTTP interfaces.
So then, Gimlet is a tool to build these kinds of APIs: simple, uses
Negroni and Gorilla’s mux
, and does pretty much everything you need
except actually write your code.
Example
Set up the app with some basic configuration: :
import "github.com/tychoish/gimlet"
app := gimlet.NewApp()
app.SetPort(9001)
app.SetDefaultVersion(1)
This sets which port the HTTP server is going to listen for requests and
configures the default version of the API. You do want all of your
endpoints prefixed with “/v<number>
” right? The default version of
the API is also avalible without the prefix, or if the version of the
route is 0
. If you don’t set it to 0
.
Then register some routes: :
app.AddRoute("/<path>").Version(<int>).Get().Handler(http.HandlerFunc)
app.AddRoute("/<path>").Version(<int>).Post().Handler(http.HandlerFunc)
app.AddRoute
returns an API route object with a set of chainable
methods for defining the routes. If you add multiple HTTP methods (GET
POST
and the like,) then Gimlet automatically defines multiple routes
with the same handler for each method.
For handlers, I typically just write functions that take arguments from
the top level context (database connections, application configuration,
etc) and returnhttp.HandlerFunc
objects. For example: :
func helloWorld(config *Configuration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
input := make(map[string]interface{})
response := make(map[string]interface{})
err := gimlet.GetJSON(input)
// do stuff here
gimlet.WriteJSON(w, response)
}
}
Gimlet has the following functions that parse JSON out of the body of a request, or add JSON output to the body of a response, they are:
WriteJSONResponse(w http.ResponseWrite, code int, data interface{})
GetJSON(r *http.Request, data interface)
Which read or write data into the interface{}
object (typically a
struct
.) The following three provide consistent response writers for
common exit codes:
WriteJSON(w http.ResponseWriter, data interface{}) // 200
WriteErrorJSON(w http.ResponseWriter, data interface{}) // 400
WriteInternalErrorJSON(w http.ResponseWriter, data interface{}) // 500
Finally, when you’ve written your app, kick it all off, with the following: :
err := app.Run()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
And that’s it. Enjoy, tell me in the comments or on the issues feed if you find something broken or confusing. Contribution welcome, of course.