Built-in Features
Lua on Beans comes packed with powerful built-in features to accelerate your development.
Generate PDF documents
Cross-site request forgery protection
Time-based one-time passwords
JSON Web Tokens
File upload handling
Scheduled background tasks
In-memory data store
On-the-fly resizing
picture_as_pdf PDF Generation
Generate PDF documents directly in Lua with support for custom fonts, images, tables, and more.
Basic PDF Example
app/controllers/reports_controller.lua
local app = {
generate_report = function()
PDFGenerator = require("pdfgenerator")
local pdf = PDFGenerator.new({ header_height = 50 })
-- Set header (runs on every page)
pdf:setHeader(function(pageId)
pdf:addParagraph("My Report - Page %s of %d" % { pageId, pdf:totalPage() },
{ fontSize = 16, alignment = "left", newPage = false })
pdf:drawLine(50, 842-50, 595-50, 842-50, 1)
end)
-- Add a page (default A4 size)
pdf:addPage()
-- Add custom fonts
pdf:addCustomFont("fonts/Helvetica.ttf", "helvetica", "normal")
pdf:addCustomFont("fonts/Helvetica-Bold.ttf", "helvetica", "bold")
-- Add an image
local imgName = pdf:addImage(LoadAsset("logo.jpg"), "jpeg")
pdf:drawImage(imgName)
pdf:moveY(10)
-- Add text with styling
pdf:addParagraph("Hello World!", {
fontSize = 24,
alignment = "center",
fontWeight = "bold"
})
pdf:moveY(10)
pdf:addParagraph("This is a sample paragraph with justified text.", {
fontSize = 12,
alignment = "justify"
})
-- Output the PDF
SetHeader("Content-Type", "application/pdf")
Write(pdf:generate())
end
}
return app
list PDF Methods Reference
| Method | Description |
|---|---|
| Pages & Layout | |
| addPage(width, height) | Add a new page (default A4: 595x842 points) |
| setHeader(fn) | Set header function for all pages |
| setFooter(fn) | Set footer function for all pages |
| totalPage() | Get total number of pages |
| generate() | Generate PDF binary string |
| Text & Fonts | |
| addParagraph(text, options) | Add styled text (fontSize, alignment, fontWeight, color) |
| addText(text, fontSize, color, alignment, width) | Add raw text |
| addCustomFont(path, name, weight) | Load custom TrueType font |
| useFont(name, weight) | Switch to a loaded font |
| getTextWidth(text, fontSize, fontWeight) | Calculate text width in points |
| Tables | |
| drawTable(options, table_options) | Draw a complete table with headers and data |
| drawTableRow(columns, row_options) | Draw a single table row |
| drawTableCell(column, options) | Draw a single table cell |
| Images | |
| addImage(data, format) | Add image (jpeg, png) and return reference |
| drawImage(name, width) | Draw image at current position |
| Drawing | |
| drawLine(x1, y1, x2, y2, width, options) | Draw a line between two points |
| drawRectangle(options) | Draw rectangle (width, height, fillColor, borderColor) |
| drawCircle(radius, borderWidth, borderStyle, borderColor, fillColor) | Draw a circle |
| drawStar(outerRadius, branches, borderWidth, borderStyle, borderColor, fillColor) | Draw a star shape |
| drawSvgPath(width, height, pathData, options) | Draw SVG path data |
| Charts | |
| BarChart(barData, labels) | Draw a bar chart |
| Cursor Position | |
| setX(x) | Set horizontal cursor position |
| setY(y) | Set vertical cursor position |
| moveX(x) | Move cursor horizontally |
| moveY(y) | Move cursor vertically |
| currentYPos() | Get current Y position |
Table Example
Creating tables with headers and data rows
pdf:drawTable({
header_columns = {
{ text = "Product", width = 200, type = "text" },
{ text = "Quantity", width = 100, type = "text" },
{ text = "Price", width = 100, type = "text" },
},
header_options = {
fontSize = 12,
fontWeight = "bold",
fillColor = "333333",
textColor = "ffffff",
alignment = "center",
},
data_columns = {
{
{ text = "Widget A", width = 200, type = "text" },
{ text = "10", width = 100, type = "text" },
{ text = "$99.00", width = 100, type = "text" },
},
{
{ text = "Widget B", width = 200, type = "text" },
{ text = "5", width = 100, type = "text" },
{ text = "$149.00", width = 100, type = "text" },
},
},
data_options = {
fontSize = 11,
alignment = "left",
fillColor = "ffffff",
oddFillColor = "f5f5f5", -- Alternate row color
evenFillColor = "ffffff",
},
}, {
padding_x = 8,
padding_y = 6,
})
security CSRF Protection
Protect your forms from cross-site request forgery attacks with built-in token generation and validation.
code In Your View (Form)
<form method="POST" action="/users">
<%- AuthenticityTokenTag() %>
<input type="text" name="name" />
<button type="submit">Create</button>
</form>
AuthenticityTokenTag() generates a hidden input with a secure token.
verified_user In Your Controller
-- At the top of your controller
CheckCSRFToken()
local app = {
create = function()
-- Token is automatically validated
-- for POST requests
local params = GetBodyParams()
-- ...
end
}
info How It Works
GetRandomBytes(64) and hashed with SHA256
CheckCSRFToken() validates the token on POST requests
password TOTP (Two-Factor Authentication)
Implement time-based one-time passwords for secure two-factor authentication, compatible with Google Authenticator, Authy, etc.
-- Enable OTP in .init.lua
OTP = require("totp")
-- Generate OTP Auth URI for QR code
local secret = "JBSWY3DPEHPK3PXP" -- User's secret (Base32)
local uri = OTP.OTPAuth(secret, "MyApp", "user@example.com")
-- Returns: otpauth://totp/MyApp:user@example.com?secret=...&issuer=MyApp&digits=6&period=30
-- Validate user-provided OTP
local is_valid = OTP.ValidateTOTP(secret, "123456", 6)
if is_valid then
-- Allow access
else
-- Reject access
end
-- Generate recovery codes (for backup)
local codes = OTP.GenerateOTPRecoveryCodes(secret, 10)
-- Returns array of 10 recovery codes
functions OTP Functions
| Function | Description |
|---|---|
| OTPAuth(secret, issuer, account) | Generate otpauth:// URI for QR codes |
| ValidateTOTP(secret, user_otp, digits) | Validate a TOTP code (checks ±1 time window) |
| GenerateOTPRecoveryCodes(secret, count) | Generate backup recovery codes |
token JWT (JSON Web Tokens)
Create and verify JSON Web Tokens for stateless authentication.
-- Create a JWT
local payload = {
header = { alg = "HS256", typ = "JWT" },
payload = {
sub = "user123",
iat = os.time(),
exp = os.time() + 3600 -- Expires in 1 hour
}
}
local token = JWT.Encode(payload, ENV['JWT_SECRET'], "HS256")
-- Verify and decode a JWT
local decoded = JWT.DecodeAndVerify(token, ENV['JWT_SECRET'], { "HS256" })
if decoded then
print(decoded.payload.sub) -- "user123"
end
-- Set JWT in a cookie
JWT.SetCookieToken(payload, ENV['JWT_SECRET'], "HS256", {
name = "access_token",
HttpOnly = true,
MaxAge = 3600,
SameSite = "Strict"
})
-- Verify JWT from cookie
local token_data = JWT.VerifyCookieToken(ENV['JWT_SECRET'], nil, { "HS256" })
upload_file File Uploads
Handle file uploads with multipart form data support and automatic image processing.
-- Enable multipart in .init.lua
PrepareMultiPartParams()
-- In your controller
local app = {
upload = function()
if Params.file then
local uuid = UuidV7()
-- File properties available:
-- Params.file.filename -- Original filename
-- Params.file.ext -- File extension
-- Params.file.content_type -- MIME type
-- Params.file.size -- File size in bytes
-- Params.file.content -- File binary content
-- Save file to disk
Barf("uploads/" .. uuid .. "." .. Params.file.ext, Params.file.content)
WriteJSON({ uuid = uuid })
end
end
}
image On-the-fly Image Resizing
Use libvips for automatic image resizing:
-- Resize to width
RunCommand("vips thumbnail " .. original .. " " .. output .. " 800")
-- Resize with specific dimensions and crop
RunCommand("vips thumbnail " .. original .. " " .. output .. " 400 --height 300 --crop centre")
schedule Cron Jobs
Schedule background tasks with cron-style syntax. Jobs run in daemon mode via OnServerHeartbeat().
Cron Job Example
app/cronjobs/cleanup_cron.lua
-- Run every minute
HandleCronJob("* * * * *", function()
Log(kLogInfo, "Running every minute: " .. os.date("%Y-%m-%d %H:%M:%S"))
end)
-- Run every 5 minutes
HandleCronJob("*/5 * * * *", function()
-- Clean up expired sessions
Adb.primary:Query("FOR s IN sessions FILTER s.expires_at < @now REMOVE s IN sessions", {
now = os.time()
})
Log(kLogInfo, "Session cleanup completed")
end)
-- Run daily at midnight
HandleCronJob("0 0 * * *", function()
-- Generate daily reports
Log(kLogInfo, "Generating daily report...")
end)
Cron Syntax
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday = 0)
│ │ │ │ │
* * * * *
storage Redis Integration
Connect to Redis for caching, sessions, and real-time features.
-- Connect in .init.lua or OnHttpRequest
local redis = require("db.redis")
Redis = redis.connect()
-- In your controller
local app = {
increment_counter = function()
local count = Redis:incr("page_views")
WriteJSON({ views = count })
end,
cache_data = function()
-- Set with expiration (60 seconds)
Redis:setex("user:123", 60, EncodeJson({ name = "John" }))
-- Get cached data
local cached = Redis:get("user:123")
if cached then
WriteJSON(DecodeJson(cached))
end
end
}
-- Don't forget to close the connection
-- unix.close(Redis.network.socket)
Pro Tips
Environment Variables: Store secrets in ENV['SECRET_KEY'] and ENV['JWT_SECRET'] for security.
Preload Assets: Preload fonts and common assets in .init.lua using the Resources table for better performance.
File Size Limit: Configure max upload size with ProgramMaxPayloadSize(10485760) (10MB default).
Cron Logging: Use Log(kLogInfo, message) to track cron job execution in your logs.