Counter Example
This example demonstrates the core Nixi + HTMX pattern: a counter that updates without page reloads.
Complete Code
#!/usr/bin/env lua
---@meta
package.path = "src/?.lua;src/?/init.lua;" .. package.path
local Nixi = require("nixi.init")
local app = Nixi.new({
host = "0.0.0.0",
port = 8080,
})
app:get("/", function(ctx)
return Nixi.Layout({
title = "Nixi Counter",
body = Nixi.div(
{ style = "text-align: center; padding: 50px;" },
Nixi.h1(nil, "Nixi Counter Demo"),
Nixi.p(nil, "Count: "),
Nixi.span({id = "count"}, "0"),
Nixi.div(
{ style = "margin-top: 20px; display: flex; gap: 10px; justify-content: center;" },
Nixi.button(
{
["hx-get"] = "/decrement",
["hx-target"] = "#count",
["hx-swap"] = "innerHTML",
class = "btn"
},
"-"
),
Nixi.button(
{
["hx-get"] = "/increment",
["hx-target"] = "#count",
["hx-swap"] = "innerHTML",
class = "btn primary"
},
"+"
)
),
Nixi.div(
{ style = "margin-top: 20px;" },
Nixi.button({
["hx-get"] = "/reset",
["hx-target"] = "#count",
["hx-swap"] = "innerHTML",
class = "btn danger"
}, "Reset")
)
),
head = [[
<style>
.btn {
padding: 12px 24px;
font-size: 20px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
font-weight: bold;
}
.btn:hover { transform: scale(1.05); }
.btn:active { transform: scale(0.95); }
.primary { background: #00ff88; color: #1a1a2e; }
.danger { background: #f44336; color: white; }
#count {
font-size: 32px;
font-weight: bold;
display: inline-block;
min-width: 60px;
}
</style>
]]
})
end)
app:get("/increment", function(ctx)
return Nixi.span({class = "nixi-pulse"}, "1")
end)
app:get("/decrement", function(ctx)
return Nixi.span({class = "nixi-pulse"}, "-1")
end)
app:get("/reset", function(ctx)
return Nixi.span(nil, "0")
end)
print("App loaded")
return app
How It Works
1. Main Page Route
The main route returns a complete HTML page with:
-
A
<span id="count">displaying the current count - Buttons with HTMX attributes for AJAX updates
2. HTMX Button Attributes
Nixi.button(
{
["hx-get"] = "/increment", -- URL to request
["hx-target"] = "#count", -- Element to update
["hx-swap"] = "innerHTML" -- How to update
},
"+"
)
What happens when clicked:
- Browser sends GET request to
/increment - Server returns new count as HTML
- HTMX replaces content of
#countelement
3. Update Endpoints
app:get("/increment", function(ctx)
count = count + 1
return Nixi.span({ id = "count" }, tostring(count))
end)
Each endpoint modifies the server-side state and returns only the HTML fragment to update.
Key Concepts Demonstrated
| Concept | Implementation |
|---|---|
| Server State | local count = 0 |
| HTMX Attributes |
hx-get, hx-target,
hx-swap
|
| Element Targeting | id = "count" and #count |
| Partial Response | Returns only span, not full page |