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.