Tutorial

This is a simple tutorial showing how to build a Gin application from the grounds up.

This tutorial showcases the use of Test-Driven Development techniques to build a simple application that allows to see a list of users, and perform basic operations on them.

For the purpose of this tutorial we have omitted API authentication. We also assume that you have Gin and MySql properly installed. If not, follow the install instructions before proceeding.

The code of this tutorial can be found in the Github repo gin-demo.

Create application

Let's create an application called demo:

$ gin new demo
Creating app demo...
  created file demo/spec/models/.gitkeep
  created file demo/app/models/.gitkeep
  created file demo/spec/spec_helper.lua
  created file demo/config/settings.lua
  created file demo/spec/controllers/1/pages_controller_spec.lua
  created file demo/db/schemas/.gitkeep
  created file demo/db/mysql.lua
  created file demo/config/nginx.conf
  created file demo/config/errors.lua
  created file demo/lib/.gitkeep
  created file demo/config/application.lua
  created file demo/app/controllers/1/pages_controller.lua
  created file demo/db/migrations/.gitkeep
  created file demo/.gitignore
  created file demo/config/routes.lua

Let's now CD in the newly created directory:

$ cd demo
Run the default tests

The autogenerated application includes a controller and its tests. Ensure that everything works properly by running busted:

$ busted

●
1 success / 0 failures / 0 pending : 0.052732 seconds.
Remove the existing controller and controller spec

We will not need the autogenerated application's controller and its test file. Go ahead and delete them:

$ rm app/controllers/1/pages_controller.lua
$ rm spec/controllers/1/pages_controller_spec.lua

Users index

Let's start by implementing a Users' index API call.

Create the Users' controller test file

Create the file ./spec/controllers/1/users_controller_spec.lua, and let's write the first test:

require 'spec.spec_helper'


describe("UsersController", function()
    describe("#index", function()
        it("shows the list of users", function()
            local response = hit({
                method = 'GET',
                path = "/users"
            })

            assert.are.equal(200, response.status)
            assert.are.same({}, response.body)
        end)
    end)
end)

As a start, we just want our Users' controller to return a successful response with an empty JSON body. Let's run the tests:

$ busted spec/controllers/1/users_controller_spec.lua

●
0 successes / 1 failure / 0 pending : 0.024976 seconds.

Failure → ./spec/controllers/1/users_controllers_spec.lua @ 6
UsersController / #index / shows the list of users
./spec/controllers/1/users_controllers_spec.lua:12: Expected objects to be the same. Passed in:
(number) 404
Expected:
(number) 200

That is to be expected, as we obviously still do not have created the users' controller nor have we specified the routes to handle the request on /users.

Add the routes

Edit the file ./config/routes.lua. Let's create a version 1 namespace, and the route to /users.

local routes = require 'gin.core.routes'

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

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

return routes
Create Users' controller

Create the file ./app/controllers/1/users_controller.lua, and add the action for the route that we have just defined:

local UsersController = {}

function UsersController:index()
    return 200, {}
end

return UsersController

Our tests will now pass:

$ busted spec/controllers/1/users_controller_spec.lua

●
1 success / 0 failures / 0 pending : 0.027029 seconds.

Congratulations! Now let's modify our controller to return the users in the database.

Setup Database

We will be using a MySql database, so let's edit the ./db/mysql.lua file with our settings.

local SqlDatabase = require 'gin.db.sql'
local Gin = require 'gin.core.gin'

-- First, specify the environment settings for this database, for instance:
local DbSettings = {
    development = {
        adapter = 'mysql',
        host = "127.0.0.1",
        port = 3306,
        database = "demo_development",
        user = "root",
        password = "",
        pool = 5
    },

    test = {
        adapter = 'mysql',
        host = "127.0.0.1",
        port = 3306,
        database = "demo_test",
        user = "root",
        password = "",
        pool = 5
    },

    production = {
        adapter = 'mysql',
        host = "127.0.0.1",
        port = 3306,
        database = "demo_production",
        user = "root",
        password = "",
        pool = 5
    }
}

-- Then initialize and return your database:
local MySql = SqlDatabase.new(DbSettings[Gin.env])

return MySql

We have now defined a connection to our MySql database.

Create a Users' table

Let's go ahead and generate a new migration for the newly created MySql connection:

$ gin generate migration
Created new migration file
  db/migrations/20131116131423.lua

Edit the file ./db/migrations/20131116131423.lua to create the users table in the up() method (called when the migration is run), and destroy it in the down() method (when the migration is rolled back), like this:

local SqlMigration = {}

-- specify the database used in this migration (needed by the Gin migration engine)
SqlMigration.db = require 'db.mysql'

function SqlMigration.up()
    -- Run your migration
    SqlMigration.db:execute([[
        CREATE TABLE users (
            id int NOT NULL AUTO_INCREMENT,
            first_name varchar(255) NOT NULL,
            last_name varchar(255),
            PRIMARY KEY (id),
            UNIQUE (first_name)
        );
    ]])
end

function SqlMigration.down()
    -- Run your rollback
    SqlMigration.db:execute([[
        DROP TABLE users;
    ]])
end

return SqlMigration

Let's now apply the migration in the test environment:

$ GIN_ENV=test gin migrate
Migrating up in test environment
==> Successfully applied migration: 20131116131423

The migration engine has created the database demo_test for us.

Create Users model

In order to return users, we first need to create a Users model. Go ahead and create the file ./app/models/users.lua, with this content:

-- gin
local MySql = require 'db.mysql'
local SqlOrm = require 'gin.db.sql.orm'

-- define
return SqlOrm.define_model(MySql, 'users')

This defines a model Users that corresponds to the database table users, for the database connection MySql.

By convention, models in Gin have plural names.

Modify Users' controller test

Let's now modify the Users' controller test to return users. Edit the file ./spec/controllers/1/users_controller_spec.lua to add some fixture users in our database:

require 'spec.spec_helper'

local MySql = require 'db.mysql'
local Users = require 'app.models.users'

local function clean_db()
    MySql:execute("TRUNCATE TABLE users;")
end


describe("UsersController", function()
    before_each(function()
        clean_db()
    end)

    after_each(function()
        clean_db()
    end)

    describe("#index", function()
        before_each(function()
            roberto = Users.create({first_name = 'roberto', last_name = 'gin'})
            hedy = Users.create({first_name = 'hedy', last_name = 'tonic'})
        end)

        after_each(function()
            roberto = nil
            hedy = nil
        end)

        it("shows the list of users ordered by first name", function()
            local response = hit({
                method = 'GET',
                path = "/users"
            })

            assert.are.equal(200, response.status)

            assert.are.same({
                [1] = hedy,
                [2] = roberto
            }, response.body)
        end)
    end)
end)

Please note that in Lua, arrays start with index 1, hence the expectations above.

Remember to clean up after your tests, this is what the MySql:execute("TRUNCATE TABLE users;") call in clean_db() is for. We are calling clean_db() before and after all tests, to ensure that the database has a clean start even in the case that previous tests were run improperly or aborted for any reason.

Also, don't forget to reset any global variables, like roberto and hedy in the example here above.

Let's run the controller tests:

$ busted spec/controllers/1/users_controllers_spec.lua

●
0 successes / 1 failure / 0 pending : 0.072839 seconds.

Failure → ./spec/controllers/1/users_controllers_spec.lua @ 31
UsersController / #index / shows the list of users ordered by first name
./spec/controllers/1/users_controllers_spec.lua:39: Expected objects to be the same. Passed in:
(table): { }
Expected:
(table): {
  [2] = {
    [last_name] = 'gin'
    [first_name] = 'roberto'
    [id] = 1 }
  [1] = {
    [last_name] = 'tonic'
    [first_name] = 'hedy'
    [id] = 2 } }

Let's go ahead and edit the controller:

local UsersController = {}

function UsersController:index()
    local Users = require 'app.models.users'
    local users = Users.all({ order = "first_name" })

    return 200, users
end

return UsersController

For optimization reasons, ensure that you require the Users model in the controller action itself, not at module level.

If we run our tests now:

$ busted spec/controllers/1/users_controllers_spec.lua

●
1 success / 0 failures / 0 pending : 0.073925 seconds.

Great! Now what about smoke testing our application in a browser?

Prepare the development environment

Ensure migrations are run in the development environment:

$ gin migrate
Migrating up in development environment
==> Successfully applied migration: 20131116131423

Now let's create some fake users in the development database, from the Gin console:

$ gin console
Loading development environment (Gin v0.1)
Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> Users = require 'app.models.users'
> Users.create({first_name = 'roberto', last_name = 'gin'})
> Users.create({first_name = 'hedy', last_name = 'tonic'})
> pp(Users.all())
{
  {
    first_name = "roberto",
    last_name = "gin",
    id = 1
  },
  {
    first_name = "hedy",
    last_name = "tonic",
    id = 2
  }
}

As we can see from the pp command (aka 'pretty print') issued in the console, our users have successfully been created in the development database. Quit the console with CTRL-C.

Let's start a server in the development environment:

$ gin start
Gin app in development was succesfully started on port 7200.

Open up a browser and point it to the API Console at the address http://localhost:7200/ginconsole.

Input http://localhost:7200/users in the API Console URL bar, and click on the HIT button. You should see the response body from your server:

[
  {
    "last_name": "tonic",
    "first_name": "hedy",
    "id": 2
  },
  {
    "last_name": "gin",
    "first_name": "roberto",
    "id": 1
  }
]

Users create

Let's now implement the Users' create API call.

For readability concerns, code related to the index action described in the previous section is partially omitted.

Add controller's test

Edit your controller test to add the tests for the create action.

require 'spec.spec_helper'

local MySql = require 'db.mysql'
local Users = require 'app.models.users'

local function clean_db()
    MySql:execute("TRUNCATE TABLE users;")
end


describe("UsersController", function()
    before_each(function()
        clean_db()
    end)

    after_each(function()
        clean_db()
    end)

    [...]

    describe("#create", function()
        it("adds a new user", function()
            local response = hit({
                method = 'POST',
                path = "/users",
                body = { first_name = 'new-user', last_name = 'gin' }
            })

            local new_user = Users.find_by({ first_name = 'new-user' })
            assert.are_not.equals(nil, new_user)

            assert.are.equal(201, response.status)
            assert.are.same(new_user, response.body)
        end)
    end)
end)

Running the tests returns an error:

$ busted spec/controllers/1/users_controllers_spec.lua

●●
1 success / 1 failure / 0 pending : 0.133645 seconds.

Failure → ./spec/controllers/1/users_controllers_spec.lua @ 47
UsersController / #create / adds a new user
./spec/controllers/1/users_controllers_spec.lua:57: Expected objects to be the same. Passed in:
(number) 404
Expected:
(number) 201

The passing test is obviously the one related to the index action.

Create the route for the create action, by editing the ./config/routes.lua file:

local routes = require 'gin.core.routes'

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

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

return routes

Edit the Users' controller in ./app/controllers/1/users_controller.lua to add the create action:

local UsersController = {}

[...]

function UsersController:create()
    local Users = require 'app.models.users'
    local new_user = Users.create(self.request.body)

    return 201, new_user
end

return UsersController

Let's run the tests again:

$ busted spec/controllers/1/users_controllers_spec.lua

●●
2 successes / 0 failures / 0 pending : 0.166405 seconds.

We are now able to allow for user creation.

Accepted params

We're currently not filtering out the params that we are allowing external callers to set in our models. In this example, for instance, we do not want an external caller to be able to set the id they want on new users, nor to make our server raise an error due to non-existent params being passed in.

Add the test:

require 'spec.spec_helper'

local MySql = require 'db.mysql'
local Users = require 'app.models.users'

local function clean_db()
    MySql:execute("TRUNCATE TABLE users;")
end


describe("UsersController", function()
    before_each(function()
        clean_db()
    end)

    after_each(function()
        clean_db()
    end)

    [...]

    describe("#create", function()
        it("adds a new user filtering out unaccepted params", function()
            local request_new_user = {
                first_name = 'new-user',
                last_name = 'gin',
                id = 400,
                nonexisent_param = 'non-existent'
            }

            local response = hit({
                method = 'POST',
                path = "/users",
                body = request_new_user
            })

            local new_user = Users.find_by({ first_name = 'new-user' })
            assert.are_not.equals(nil, new_user)

            assert.are.equal(201, response.status)

            assert.are.same('new-user', new_user.first_name)
            assert.are.same('gin', new_user.last_name)
            assert.are.not_equals(400, new_user.id)
            assert.are.not_equals('non-existent', new_user.nonexisent_param)
        end)
    end)
end)

Run the tests:

$ busted spec/controllers/1/users_controllers_spec.lua

●●
1 success / 1 failure / 0 pending : 0.138575 seconds.

Failure → ./spec/controllers/1/users_controllers_spec.lua @ 47
UsersController / #create / adds a new user filtering out unaccepted params
./spec/controllers/1/users_controllers_spec.lua:62: Expected objects to not be equal. Passed in:
(nil)
Did not expect:
(nil)

We can see that the user did not get created. If we open up the test logs (file logs/test-error.log), we see:

2013/11/16 14:09:28 [error] 11545#0: *1 lua entry thread aborted: runtime error: /usr/local/share/lua/5.1/gin/core/router.lua:145: /usr/local/share/lua/5.1/gin/db/sql/mysql/adapter.lua:94: bad mysql result: Unknown column 'nonexisent_param' in 'field list': 1054 42S22
stack traceback:
coroutine 0:
    [C]: in function 'error'
    /usr/local/share/lua/5.1/gin/core/router.lua:145: in function 'call_controller'
    /usr/local/share/lua/5.1/gin/core/router.lua:74: in function 'handler'
    [string "content_by_lua"]:1: in function <[string "content_by_lua"]:1>, client: 127.0.0.1, server: , request: "POST /users? HTTP/1.1", host: "127.0.0.1"

This explains why our user did not get created, since there's an Unknown column 'nonexisent_param' in 'field list'.

Let's implement the filter with the controller's accepted_params method:

local UsersController = {}

[...]

function UsersController:create()
    local Users = require 'app.models.users'

    local params = self:accepted_params({ 'first_name', 'last_name' }, self.request.body)
    local new_user = Users.create(params)

    return 201, new_user
end

return UsersController

Our tests are now successful:

$ busted spec/controllers/1/users_controllers_spec.lua

●●
2 successes / 0 failures / 0 pending : 0.138814 seconds.

User Show

Let's finishe this tutorial by using a named route which implements the Users' show API call.

For readability concerns, code related to the index and create actions described in the previous sections is partially omitted.

Add the test for the show method:

require 'spec.spec_helper'

local MySql = require 'db.mysql'
local Users = require 'app.models.users'

local function clean_db()
    MySql:execute("TRUNCATE TABLE users;")
end


describe("UsersController", function()
    before_each(function()
        clean_db()
    end)

    after_each(function()
        clean_db()
    end)

    [...]

    describe("#show", function()
        before_each(function()
            roberto = Users.create({first_name = 'roberto', last_name = 'gin'})
        end)

        after_each(function()
            roberto = nil
        end)

        it("shows a user", function()
            local response = hit({
                method = 'GET',
                path = "/users/roberto"
            })

            assert.are.equal(200, response.status)
            assert.are.same(roberto, response.body)
        end)
    end)
end)

We consider first_name as being the friendly identifier for a user. This is why we have a uniqueness constraint at database level on the column first_name, as defined in our migration here above.

Test fails with a 404:

$ busted spec/controllers/1/users_controllers_spec.lua

●●●
2 successes / 1 failure / 0 pending : 0.191136 seconds.

Failure → ./spec/controllers/1/users_controllers_spec.lua @ 82
UsersController / #show / shows a user
./spec/controllers/1/users_controllers_spec.lua:88: Expected objects to be the same. Passed in:
(number) 404
Expected:
(number) 200

Add the named route :first_name in ./config/routes.lua:

local routes = require 'gin.core.routes'

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

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

return routes

Implement the show code the controller:

local UsersController = {}

[...]

function UsersController:show()
    local Users = require 'app.models.users'
    local user = Users.find_by({ first_name = self.params.first_name })

    return 200, user
end

return UsersController

Test now succeed:

$ busted spec/controllers/1/users_controllers_spec.lua

●●●
3 successes / 0 failures / 0 pending : 0.199211 seconds.

One last thing: we need to return a 404 if the user cannot be found. The test for the show action becomes:

require 'spec.spec_helper'

local MySql = require 'db.mysql'
local Users = require 'app.models.users'

local function clean_db()
    MySql:execute("TRUNCATE TABLE users;")
end


describe("UsersController", function()
    before_each(function()
        clean_db()
    end)

    after_each(function()
        clean_db()
    end)

    [...]

    describe("#show", function()
        describe("when the user can be found", function()
            before_each(function()
                roberto = Users.create({first_name = 'roberto', last_name = 'gin'})
            end)

            after_each(function()
                roberto = nil
            end)

            it("shows a user", function()
                local response = hit({
                    method = 'GET',
                    path = "/users/roberto"
                })

                assert.are.equal(200, response.status)
                assert.are.same(roberto, response.body)
            end)
        end)

        describe("when the user cannot be found", function()
            it("returns a 404", function()
                local response = hit({
                    method = 'GET',
                    path = "/users/roberto"
                })

                assert.are.equal(404, response.status)
                assert.are.same({}, response.body)
            end)
        end)
    end)
end)

And the controller implementation:

local UsersController = {}

[...]

function UsersController:show()
    local Users = require 'app.models.users'
    local user = Users.find_by({ first_name = self.params.first_name })

    if user then
        return 200, user
    else
        return 404
    end
end

return UsersController

This concludes the tutorial. To see the complete code for this application, please checkout the Github repo gin-demo.