Beta documentation. This is an early preview — content is still in active development. Feedback helps shape the final release. Share your thoughts or join the discussion.

Page-Based Code

As your app grows, `lua_init` becomes a monster. Hundreds of lines. Hard to find things. One mistake breaks everything.

On this page

The problem

As your app grows, lua_init becomes a monster. Hundreds of lines. Hard to find things. One mistake breaks everything.

loadpage() solves this by letting you store handler code in database pages instead of one giant script.

How it works

Two ways to define a handler

-- Way 1: Inline function (live in lua_init)
app:get("/api/status", function(ctx)
    return ctx:json({ ok = true })
end)

-- Way 2: String reference (handler lives in a DB page)
app:get("/blog", "blog_list")        -- loads from page "blog_list", type=29
app:get("/blog/:slug", "blog_post")  -- loads from page "blog_post", type=29

When Wapka sees a string handler name, it calls loadpage(name) — which fetches the page's code from the database and runs it.

What's type=29?

Pages have a type field. type=29 means "Lua Script." These pages store your handler code. type=0 means "Template" (for ctx:render).

A page can have multiple code entries. They're concatenated in position order and executed as one script.

Organizing a real app

lua_init (stays tiny — just setup)

local app = framework()
app:use("cors")
app:use("csrf")

-- Route definitions (just strings pointing to pages)
app:get("/", "home")
app:get("/about", "about")
app:get("/blog", "blog_list")
app:get("/blog/:slug", "blog_post")
app:post("/contact", "contact_form")
app:group("/admin", function()
    app:get("/dashboard", "admin_dashboard", { middleware = "admin" })
end)

Page "blog_list" → type=29

-- This code runs ONLY when someone visits /blog
local result = api.posts.list({
    forumid = 1, status = 1, order = "id_desc", limit = 10
})
return ctx:render("blog_list", {
    posts = result.items,
    title = "Blog"
})

Page "blog_post" → type=29

-- This code runs ONLY when someone visits /blog/some-slug
local slug = ctx.params.slug
local result = api.posts.list({ search = slug })
if result.total == 0 then
    return ctx:error("Post not found", 404)
end
return ctx:render("post", { post = result.items[1] })

Multiple code entries on one page

Split large pages by concern — helpers, routes, handlers:

Page "index"
  → Code pos=0: helper functions
  → Code pos=1: route definitions
  → Code pos=2: handler logic

All three are concatenated and run together. Position controls order.

The loadpage() function

You can call loadpage() from anywhere — not just routes:

-- Redirect to login page if not authenticated
if not env.is_user then
    loadpage("login")
    return
end

-- Reusable code module
loadpage("email_processor")  -- run another page's code

Recursive calls are safe — second call to the same page is skipped.

Function handlers vs string handlers

Function Handler String Handler
Storage Inline in lua_init DB page type=29
Loaded Always (on app definition) Only when route matches
Edit live Must edit lua_init Edit via code editor, immediate
Best for Small API routes Content pages, complex logic
Organization Everything in one place One page = one concern

Default behavior

If lua_init is empty, Wapka auto-generates:

local app = framework()
app:any("/*path", "index")
app:run()

Create a page named "index" with type=29 code, and it handles every request.

Next: Input Validation — protect your app from bad data.

Previous Hooks Next Input Validation