Lapis

Lapis

A web framework for Lua or MoonScript

Install
luarocks install lapis
January 18th, 2014: Lapis v0.0.10 is released, read more on the changelog

What is it?

Lapis is a framework for building web applications using MoonScript or Lua that runs inside of a customized version of Nginx called OpenResty.

Here’s what it looks like:

lapis = require "lapis"

class extends lapis.Application
  -- Define a basic pattern that matches /
  "/": =>
    profile_url = @url_for "profile", name: "leafo"
    @html ->
      h2 "Welcome!"
      text "Go to my "
      a href: profile_url, "profile"

  -- Define a named route pattern with a variable called name
  [profile: "/:name"]: =>
    @html ->
      div class: "profile", ->
        text "Welcome to the profile of ", @params.name
local lapis = require "lapis"
local lua = require "lapis.lua"

lua.class({
  -- Define a basic pattern that matches /
  ["/"] = function(self)
    local profile_url = self:url_for("profile", {name = "leafo"})
    return self:html(function()
      h2("Welcome!")
      text("Go to my ")
      a({href = profile_url}, "profile")
    end)
  end,

  -- Define a named route pattern with a variable called name
  [{profile = "/:name"}] = function(self)
    return self:html(function()
      div({class = "profile"}, function()
        text("Welcome to the profile of " .. self.params.name)
      end)
    end)
  end
}, lapis.Application)

How does it work?

Lua is run directly inside of the Nginx worker, giving you the smallest barrier between the webserver and your code. OpenResty executes your Lua/MoonScript with LuaJIT, so it’s blazing fast. Have a look at Web Framework Benchmarks just to see how OpenResty stacks up against other platforms.

Nginx’s event loop is used for all asynchronous actions, including HTTP requests and database queries. With the power of Lua coroutines code is written synchronously but runs asynchronously, without all that callback spaghetti seen in other asynchronous platforms. It’s fast, easy to read, and easy to write.

Perform HTTP requests and other asynchronous operations freely without being concerned about blocking your application and killing your throughput.

. . .

What does it come with?

Lapis includes URL routing, HTML Templating, CSRF and Session support, PostgreSQL backed models, schema generation and migrations in addition to a collection of useful functions needed when developing a website.

Get a powerful abstraction layer over your database tables just by sub-classing Model.

import Model from require "lapis.db.model"

-- Create a model, automatically backed by the table `users`
class Users extends Model

-- fetch some rows from the table
elderly_users = Users\select "where age > ? limit 5", 10

random_user = Users\find 1233 -- find by primary key

lee = Users\find name: "Lee", email: "leemiller@example.com"

-- create a new row and edit it
user = Users\create {
  name: "Leaf"
  email: "leaf@example.com"
  age: 6
}

user\update age: 10

user\delete!
local lua = require "lapis.lua"
local Model = require("lapis.db.model").Model

-- Create a model, automatically backed by the table `users`
local Users = class({}, Model)

-- fetch some rows from the table
local elderly_users = Users:select("where age > ? limit 5", 10)

local random_user = Users:find(1233) -- find by primary key

local lee = Users:find { name = "Lee", email = "leemiller@example.com" }

-- create a new row and edit it
local user = Users:create {
  name = "Leaf"
  email = "leaf@example.com"
  age = 6
}

user:update { age = 10 }

user:delete()

Write your templates with high composability by using MoonScript directly. No need to learn any goofy templating syntax with arbitrary restrictions.

Either inline the HTML template in the action or create a separate view like so. Views are just classes, feel free to use inheritance and mix and match methods as much as you like.

-- views/index.moon
import Widget from require "lapis.html"

class Index extends Widget
  content: =>
    h1 class: "header", "Hello"

    @user_panel!
    div class: "body", ->
      text "Welcome to my site!"

  user_panel: =>
    return unless @current_user
    div class: "user_panel", "Welcome back " .. @current_user.name
local lua = require "lapis.lua"
local Widget = require("lapis.html").Widget

return lua.class({
  content = function(self)
    h1({ class = "header" }, "Hello")

    self:user_panel()
    div({ class = "body"}, function()
      text("Welcome to my site!")
    end)
  end,

  user_panel = function(self)
    if not self.current_user then
      return
    end

    div({ class = "user_panel" }, "Welcome back " .. self.current_user.name)
  end
}, Widget)

Using all the provided tools we can quickly and logically construct high performance and low memory web applications. Here’s a more complicated example complete with forms, CSRF protection, and various database queries.

lapis = require "lapis"
import Model from require "lapis.db.model"
import respond_to, capture_errors from require "lapis.application"
csrf = require "lapis.csrf"

class Users extends Model

class extends lapis.Application
  -- Execute code before every action
  @before_filter =>
    @csrf_token = csrf.generate_token @

  [list_users: "/users"]: =>
    users = Users\select! -- `select` all the users

    -- Render HTML inline for simplicity
    @html ->
      ul ->
        for user in *users
          li ->
            a href: @url_for("user", user.id), user.name

  [user: "/profile/:id"]: =>
    user = Users\find id: @params.id
    return status: 404 unless user
    @html -> h2 user.name

  -- Respond to different HTTP actions to do the right thing
  [new_user: "/user/new"]: respond_to {
    POST: capture_errors =>
      csrf.assert_token @
      Users\create name: @params.username
      redirect_to: @url_for "list_users"

    GET: =>
      @html ->
        form method: "POST", action: @url_for("new_user"), ->
          input type: "hidden", name: "csrf_token", value: @csrf_token
          input type: "text", name: "username"
  }
local lapis = require "lapis"
local csrf = require "lapis.csrf"
local lua = require "lapis.lua"

local Model = require("lapis.db.model").Model

local application = require "lapis.application"
local respond_to, capture_errors, = application.respond_to, application.capture_errors

local app = class({
  -- Render HTML inline for simplicity
  [{list_users = "/users"}] = function(self)
    local users = Users:select() -- `select` all the users
    return self:html(function()
      ul(function()
        for _, user in ipairs(users) do
          li(function()
            a({ href = self:url_for("user", user.id)}, user.name)
          end)
        end
      end)
    end)
  end,

  [{user = "/profile/:id"}] = function(self)
    local user = Users:find { id = self.params.id }
    if not user then
      return { status = 404 }
    end

    self:html(function()
      h2(user.name)
    end)
  end,

  -- Respond to different HTTP actions to do the right thing
  [{new_user = "/user/new"}] = respond_to {
    POST = capture_errors(function()
      csrf.assert_token(self)
      Users:create { name = self.params.username }
      return { redirect_to: self.url_for("list_users") }
    end),

    POST = function()
      return self:html(function()
        form({ method = "POST", actionn = self.url_for("new_user")}, function()
          input { type = "hidden", name = "csrf_token", value = self.csrf_token }
          input { type = "text", name = "username" }
        end)
      end)
    end
  },


}, lapis.Application)

-- Execute code before every action
app.before_filter(function(self)
  self.csrf_token = csrf.generate_token(self)
end)

return app

Where can I learn more?

For a guide and tutorial to Lapis, consult the reference manual.

The source of Lapis can be found on Github and issues can be reported on the issues tracker.

MoonRocks is an open source application written in Lapis. It is a public Lua Rock repository and the source can be found on GitHub.

Anything else I should know?

You can deploy a new Lapis application in a few minutes.

If you don’t mind using Heroku then it’s just a matter of using the Lua Buildpack and installing the OpenResty module.

You can use most existing Lua libraries with Lapis with no problems. Here are some libraries you might find useful:

About

Lapis would not be possible without the following projects:

Lapis is licensed under the MIT license.

Lapis is written by @moonscript.

Fork me on GitHub