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.