Quick Start

Let's build your first Nixi application! By the end of this guide, you'll have a working counter app with reactive updates.

Create a New Project

# Using Nixi CLI (after global installation)
nixi new myapp

# Or clone and run directly
git clone https://github.com/ijadux2/nixi.git
cd nixi
./bin/nixi new my-counter

# Or with nix run
nix run --command "nixi new my-counter"

This creates a new project directory with the following structure:

myapp/
├── src/
│   ├── nixi/            # Core framework modules
│   │   ├── init.lua     # Main router
│   │   ├── style.lua    # CSS DSL
│   │   ├── htmx.lua     # HTMX bindings
│   │   ├── component.lua # HTML element creators
│   │   ├── ui/          # UI component library
│   │   │   ├── init.lua
│   │   │   ├── button.lua
│   │   │   ├── input.lua
│   │   │   ├── card.lua
│   │   │   └── theme.lua
│   │   └── ...
│   └── app.lua          # Your application code
├── public/              # Static files
├── server.lua          # Development server
└── package.json        # Project metadata

Run the Server

cd my-counter
lua server.lua

Visit http://127.0.0.1:3000 to see your app.

Your First Route

Open src/app.lua and modify it:

#!/usr/bin/env lua
---@meta

package.path = "src/?.lua;src/?/init.lua;" .. package.path

local Nixi = require("nixi.init")
_G.Nixi = Nixi

-- Create application instance
local app = Nixi.new({
    host = "127.0.0.1",
    port = 3000,
})

-- Define the home route
app:get("/", function(ctx)
    return Nixi.html([[
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>My First Nixi App</title>
        </head>
        <body style="text-align: center; padding: 50px;">
            <h1>Hello from Nixi!</h1>
            <p>A Lua web framework with HTMX</p>
        </body>
        </html>
    ]])
end)

print("App loaded")
return app

Adding HTMX Interactivity

Let's add a counter that increments without page reloads:

local count = 0

-- Main page route
app:get("/", function(ctx)
    return Nixi.Layout({
        title = "Nixi Counter",
        body = Nixi.div(
            { style = "text-align: center; padding: 50px;" },
            Nixi.h1(nil, "Counter Demo"),
            Nixi.p(nil, "Count: "),
            Nixi.span({ id = "count" }, tostring(count)),
            Nixi.div(
                { style = "margin-top: 20px; display: flex; gap: 10px;" },
                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"
                    },
                    "+"
                )
            )
        ),
        head = [[
            <style>
                .btn { padding: 12px 24px; font-size: 20px; border: none; border-radius: 8px; cursor: pointer; }
                .primary { background: #00ff88; color: #1a1a2e; }
            </style>
        ]]
    })
end)

-- Increment endpoint
app:get("/increment", function(ctx)
    count = count + 1
    return Nixi.span({ id = "count" }, tostring(count))
end)

-- Decrement endpoint
app:get("/decrement", function(ctx)
    count = count - 1
    return Nixi.span({ id = "count" }, tostring(count))
end)

Understanding the Code

1. Setting Up Package Path

package.path = "src/?.lua;src/?/init.lua;" .. package.path
local Nixi = require("nixi.init")
_G.Nixi = Nixi

2. Creating an App

local app = Nixi.new({
    host = "127.0.0.1",
    port = 3000,
})

2. Defining Routes

app:get("/", function(ctx)
    -- Handler code
end)

3. HTMX Attributes

Attribute Purpose
hx-get="/endpoint" Make AJAX GET request to endpoint
hx-target="#id" Target element to swap content into
hx-swap="innerHTML" Replace inner HTML of target

Building for Production

nixi build
# Output in ./dist directory

CSS DSL with For-Loops

Lua's for-loops enable generating hundreds of utility classes efficiently:

-- Generate spacing utilities (.m-1 to .m-16)
for i = 1, 16 do
    Nixi.style.rule(".m-" .. i, {
        margin = (i * 0.25) .. "rem"
    })
    Nixi.style.rule(".p-" .. i, {
        padding = (i * 0.25) .. "rem"
    })
end

-- Generate flexbox utilities
for _, align in ipairs({"start", "center", "end", "stretch"}) do
    Nixi.style.rule(".items-" .. align, {
        alignItems = align == "start" and "flex-start" or align == "end" and "flex-end" or align
    })
end

-- Generate color utilities
local colors = {
    { name = "primary", value = "#7aa2f7" },
    { name = "danger", value = "#f7768e" },
    { name = "success", value = "#9ece6a" },
}
for _, color in ipairs(colors) do
    Nixi.style.rule(".text-" .. color.name, { color = color.value })
    Nixi.style.rule(".bg-" .. color.name, { backgroundColor = color.value })
end