Built-in Features

Lua on Beans comes packed with powerful built-in features to accelerate your development.

picture_as_pdf PDF Generation

Generate PDF documents directly in Lua with support for custom fonts, images, tables, and more.

code

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_chart

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

check_circle Token is generated with GetRandomBytes(64) and hashed with SHA256
check_circle Stored in HttpOnly cookie with SameSite=Strict for 30 minutes
check_circle 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().

description

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)
lightbulb

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.