The idea
Middleware runs before your route handler. One line per feature — no setup code inside every handler.
local app = framework()
app:use("cors") -- done. every response gets CORS headers.
app:use("json") -- done. every request body is auto-parsed.
app:use("auth") -- done. unauthenticated requests get 401.
Built-in middleware
Six battle-tested middlewares ship with Wapka:
| String | What it does |
|---|---|
"cors" |
Adds CORS headers — echoes the request's Origin |
"json" |
Parses JSON request body into ctx.body |
"form" |
Parses URL-encoded form body into ctx.body |
"auth" |
Requires logged-in user — 401 if not |
"admin" |
Requires site admin — 403 if not |
"csrf" |
Validates CSRF token on POST/PUT/DELETE requests |
Register them in order. Global middleware first, then your routes:
local app = framework()
-- Global: applied to every route
app:use("cors")
app:use("json")
-- Public routes
app:get("/", homeHandler)
app:get("/blog/:slug", postHandler)
-- Admin-only routes
app:group("/admin", function()
app:get("/dashboard", adminHome, { middleware = { "auth", "admin" } })
end)
Custom middleware
A middleware is just a function that receives ctx. Use it for logging, timing, custom auth:
-- Log every request
app:use(function(ctx)
log.info(req.method .. " " .. req.path)
end)
-- Track request timing
app:use(function(ctx)
ctx._start = os.clock()
end)
Custom middleware can set data on ctx for later handlers:
app:use(function(ctx)
local token = req.headers["x-api-key"]
if token then
ctx.api_client = verifyApiKey(token)
end
end)
app:get("/api/private", function(ctx)
if not ctx.api_client then
return ctx:error("Invalid API key", 401)
end
return { data = getPrivateData(ctx.api_client) }
end)
Route-level middleware
Pass middleware in the route options to apply it only to specific routes:
-- Only /api routes need JSON parsing
app:group("/api", function()
app:get("/users", listUsers, { middleware = "auth" })
app:post("/users", createUser, { middleware = { "auth", "json" } })
app:get("/public", publicData) -- no middleware
end)
CSRF in action
The CSRF middleware checks every POST/PUT/DELETE for a valid token. Exempt webhooks:
app:use("csrf")
-- Protected form (CSRF checked)
app:post("/profile", updateProfile)
-- Webhook (CSRF skipped)
app:post("/webhook/stripe", stripeHandler, { csrf = false })
Middleware execution order
- Global middleware (registered first runs first)
- Route-level middleware
- Route handler
- Hooks (
before→ middleware+handler →after)
Register "auth" and "admin" last so body parsing happens first:
app:use("cors") -- 1st
app:use("json") -- 2nd (parse body before checking auth)
app:use("form") -- 3rd
-- routes here get parsed body + CORS
app:group("/admin", function()
app:get("/dashboard", handler, { middleware = { "auth", "admin" } })
end)
Next: Hooks — run code before/after every request.