Lapis is a framework for building web applications in Lua (or MoonScript) that primarily targets OpenResty, a high performance web platform that runs on a customized version of Nginx. Lapis can also be used in other server environments, being compatible with any modern version of Lua.
local lapis = require "lapis"
local app = lapis.Application()
app:match("/", function(self)
return "Hello world!"
end)
return app
lapis = require "lapis"
class extends lapis.Application
"/": =>
"Hello world!"
With OpenResty, Lua is run directly inside of the Nginx worker using LuaJIT, giving you the smallest barrier between the webserver and your code. Have a look at Web Framework Benchmarks just to see how OpenResty stacks up against other platforms.
Utilizing the power of Lua coroutines, you can write clean code that looks synchronous but can achieve high throughput by automatically running asynchronously without blocking. Networking operations like database queries and HTTP requests will automatically yield to allow for handling concurrent requests, all without all that callback spaghetti seen in other asynchronous platforms. It’s fast, easy to read, and easy to write.
Lapis includes URL routing, HTML Templating, CSRF Protection and Session support, PostgreSQL/MySQL/SQLite backed models, schema generation and migrations in addition to a collection of useful functions needed when developing a website.
local lapis = require "lapis"
local app = lapis.Application()
-- Define a basic pattern that matches /
app:match("/", function(self)
local profile_url = self:url_for("profile", {name = "leafo"})
-- Use HTML builder syntax helper to quickly and safely write markup
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
app:match("profile", "/:name", function(self)
return self:html(function()
div({class = "profile"},
"Welcome to the profile of " .. self.params.name)
end)
end)
return app
lapis = require "lapis"
class extends lapis.Application
-- Define a basic pattern that matches /
"/": =>
profile_url = @url_for "profile", name: "leafo"
-- Use HTML builder syntax helper to quickly and safely write markup
@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
Get a powerful abstraction layer over your database tables just by
sub-classing Model
:
local Model = require("lapis.db.model").Model
-- Create a model, backed by the table `users`
local Users = Model:extend("users")
-- 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()
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!
Write your templates either in etlua or in pure Lua/MoonScript.
The Widget
base class allows you to organize your templates as modules,
enabling you to use inheritance and mixins to mix and match methods combined
with the HTML builder syntax that lets you express HTML with the full power of
the language you're already using.
The HTML builder syntax makes you immune to cross site scripting attacks from user-provided input by ensuring all written content is escaped correctly.
<!-- views/index.etlua -->
<h1 class="header"> "Hello" </h1>
if current_user then
<div class="user_panel">
Welcome back current_user.name
</div>
end
<div class="body">
Welcome to my site
</div>
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
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.
local lapis = require "lapis"
local Model = require("lapis.db.model").Model
local capture_errors = require("lapis.application").capture_errors
local csrf = require "lapis.csrf"
local Users = Model:extend("users")
local app = lapis.Application()
app:before_filter(function(self)
self.csrf_token = csrf.generate_token(self)
end)
app:get("list_users", "/users", function(self)
self.users = Users:select() -- `select` all users
return { render = true }
end)
app:get("user", "/profile/:id", function(self)
local user = Users:find({ id = self.params.id })
if not user then
return { status = 404 }
end
return { render = true }
end)
app:post("new_user", "/user/new", capture_errors(function(self)
csrf.assert_token(self)
Users:create({
name = assert_error(self.params.username, "Missing username")
})
return { redirect_to = self:url_for("list_users") }
end))
app:get("new_user", "/user/new", function(self)
return { render = true }
end)
return app
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"
The Reference Manual is both a complete guide and a tutorial to using Lapis.
The source of Lapis can be found on Github and issues can be reported on the issues tracker.
LuaRocks.org is an open source application written in Lapis. It is the public host for all Lua Rocks and the source can be found on GitHub.
You can use most existing Lua libraries with Lapis with no problems. Here are some libraries you might find useful:
lapis-eswidget
— Base widget class for aggregating front-end assets with esbuildlapis-console
— Interactive MoonScript console for Lapis that runs inside of your browserlapis-exceptions
— Exception tracking and reportinglapis-bayes
— General purpose Bayes classification for Spam, Fraud, etc.web_sanitize
— HTML sanitizationtableshape
— Robus input validation and verificationmagick
— ImageMagick bindingscloud_storage
— Support for Google Cloud StorageLapis would not be possible without the following projects:
Lapis is licensed under the MIT license.
Lapis is written by @moonscript.