gin

GIN: a JSON-API framework in Lua

By Roberto Ostinelli / June 18, 2015

GIN is a fast, low-latency, low-memory footprint, web JSON-API framework with Test Driven Development helpers and patterns. It is a framework that I open sourced some time ago, as an experiment. 

It all started when I heard so many good things about Lua that I wanted to see it in action and find a project where I could unleash its power. Being an API fan, it came natural for me to build a JSON-API server framework. And GIN was born.

GIN is currently in its early stage, but it already enables fast development, TDD and ease of maintenance. It is helpful when you need an extra-boost in performance and scalability, since it is entirely written in Lua and it runs embedded in a packaged version of nginx called OpenResty. For those not familiar with Lua, don't let that scare you away: Lua is really easy to use, very fast and simple to get started with.

Controllers

The syntax of a controller is extremely simple. For instance, a simple controller that returns an application's version information looks like:

LUA
local InfoController = {}

function InfoController:root()
    return 200, { id = 'my_api', description = 'An API server powered by GIN.' }
end

return InfoController

The return statement specifies:

  • The HTTP code 200
  • The body of the response, which gets encoded by GIN into JSON as:
JAVASCRIPT
{
  "id": "my_api",
  "description": "An API server powered by GIN."
}

Most of standard JSON-API paradigms are already embedded in GIN.

Versioning

For instance, Versioning is extremely simple. Major versioning is baked into GIN. Based on directory naming conventions, the controllers that correspond to the versioning specified in the header will be called by GIN automatically. Gin uses the HTTP header Accept to version your API. The header has the format:

application/vnd.{application-name}.v{version}+json

So for instance, if your application name (as defined in the file ./config/application.lua) is demo, a client trying to access the version 1 of your API will have to send the header:

Accept: application/vnd.demo.v1+json

Gin only accepts requests that provide JSON in the body, hence the +json portion of the Accept header.

Routing

Routing is very easy, too. For instance, this is an example of a routing file that handles multiple versions:

LUA
local routes = require 'gin.core.routes'

-- define version 1
local v1 = routes.version(1)

-- define routes
v1:GET("/users", { controller = "users", action = "index" })
v1:POST("/users", { controller = "users", action = "create" })

-- define version 2
local v2 = routes.version(2)

-- define routes
v2:GET("/users", { controller = "users", action = "index" })
v2:POST("/users", { controller = "users", action = "create" })
v2:GET("/users/:id", { controller = "users", action = "show" })

return routes

Errors

Moreover, Errors are defined globally at application level. This allows to keep track of error numbering and descriptions in a single file. For example, an error file is defined as:

LUA
local Errors = {
    [1000] = { status = 400, message = "Invalid request.", headers = { ["X-Header"] = "header" } },
}
return Errors

The global variable Errors contains the entries for all possible errors of your application. Every item in this table defines an error, where:

  • The 1000 key is the error number.
  • status (required): is the HTTP status code.
  • message (required): is the error description.
  • headers (optional): are the additional headers to be returned in the response.

When this is defined, then raising these errors from a controller is as simple as calling:

LUA
self:raise_error(1000)

As per this example, this will return a HTTP status 400, with the specified headers and the JSON body:

JAVASCRIPT
{
  "code": 1000,
  "message": "Invalid request."
}

Testing

However, this wouldn't be a complete framework if it didn't provide an easy way to test your application. The main testing framework embedded in GIN is Busted, which has an RSpec-like syntax. If you ever used Jasmine you'll feel right at home. In addition, Gin provides test helpers for you to use.

Controller Tests in Gin are actually integration tests. An instance of OpenResty nginx will be started and a real request will be issued to a test server. The main Gin test helper you'll need to test Controllers is the method hit, that will perform the integration test for you.

Here's an example of a controller test (file located in ./spec/controllers/1/info_controller_spec.lua), that is a valid test for the InfoController shown here above:

LUA
require 'spec.spec_helper'

describe("InfoController", function()
    describe("#root", function()
        it("responds with application info", function()
            local response = hit({
                method = 'GET',
                path = "/"
            })

            assert.are.same(200, response.status)
            assert.are.same({ id = 'my_api', description = 'An API server powered by GIN.' }, response.body)
        end)
    end)
end)

Others

GIN also has models and support for multiple databases, and includes a migration engine. Furthermore, it is packed with a lot of other features, such as:

  • An API Console (which allows you to try out your API from a web interface).
  • Support for multiple environments.
  • A client console.

Alright, I want to see it!

GIN main website is gin.io, where you'll find full documentation, and even a full tutorial on how to Test Drive your Development with GIN. Full code is available on Github.

Would this be worthy more than a simple experiment?