Routing

Routing is the foundation of any Nixi application. Routes map URL paths to handler functions that process requests and return responses.

Basic Routes

HTTP Methods

Nixi supports all standard HTTP methods:

-- GET request (default for browsing)
app:get("/path", handler)

-- POST request (form submissions, API calls)
app:post("/path", handler)

-- PUT request (updates)
app:put("/path", handler)

-- DELETE request
app:delete("/path", handler)

Dynamic Routes

Use parameters in your paths with the :param syntax:

-- User profile route
app:get("/users/:id", function(ctx)
    local user_id = ctx.params.id
    return Nixi.p(nil, "User ID: " .. user_id)
end)

-- Blog post route
app:get("/blog/:year/:month/:slug", function(ctx)
    local year = ctx.params.year
    local month = ctx.params.month
    local slug = ctx.params.slug
    return Nixi.p(nil, year .. "/" .. month .. "/" .. slug)
end)

Query Parameters

Access query string parameters via ctx.request.query:

-- URL: /search?q=lua&page=2
app:get("/search", function(ctx)
    local query = ctx.request.query.q
    local page = ctx.request.query.page or "1"
    
    return Nixi.p(nil, "Searching for: " .. query .. " on page " .. page)
end)

Route Handler

The handler function receives a context object and returns content.

Handler Return Types

app:get("/example", function(ctx)
    -- Return a string (auto-wrapped in response)
    return "Simple text response"
    
    -- Or return a response table for more control
    return {
        status = 200,
        headers = { ["Content-Type"] = "text/plain" },
        body = "Custom response"
    }
end)

Middleware

Middleware functions process requests before they reach your route handler:

-- Logger middleware
local function logger(ctx)
    print(string.format("[%s] %s", ctx.request.method, ctx.request.path))
end

-- Authentication middleware
local function require_auth(ctx)
    local token = ctx.request.headers["Authorization"]
    if not token then
        return {
            status = 401,
            body = "Unauthorized"
        }
    end
end

-- Apply middleware to all routes
app:use(logger)
app:use(require_auth)

Response Helpers

JSON Response

app:get("/api/user", function(ctx)
    return Nixi.json({
        name = "John",
        email = "john@example.com",
        age = 30
    })
end)

Redirect

app:get("/old-page", function(ctx)
    return Nixi.redirect("/new-page")
end)

Error Response

app:get("/protected", function(ctx)
    local authorized = false
    if not authorized then
        return Nixi.error(403, "Access denied")
    end
    return "Secret content"
end)

Route Matching

Pattern Matches Example
/ Exact path /
/about Exact path /about
/:id Single segment /123, /abc
/:category/:id Multiple segments /users/123

File-Based Routing

Nixi supports automatic route discovery from the routes/ directory. This is similar to Next.js file-based routing.

Project Structure

my-app/
├── app.lua           # Main application
├── routes/           # File-based routes (auto-discovered)
│   ├── index.lua    # → /
│   ├── about.lua    # → /about
│   ├── users.lua    # → /users
│   └── users/
│       └── [id].lua  # → /users/:id
├── layouts/         # Layout templates
│   └── default.lua
└── server.lua       # Development server

Route File Format

Route files return a table with the route configuration:

-- routes/index.lua
return {
    methods = { "GET" },
    name = "home",
    layout = "default",
    
    handler = function(ctx)
        return Nixi.html([[
            <div class="container">
                <h1>Welcome!</h1>
            </div>
        ]])
    end,
}

Dynamic Routes

Use brackets [] for dynamic segments:

-- routes/users/[id].lua
return {
    methods = { "GET" },
    name = "users.show",
    
    handler = function(ctx)
        local user_id = ctx.params.id
        return Nixi.html("User ID: " .. user_id)
    end,
}

-- routes/posts/[year]/[month]/[slug].lua
return {
    methods = { "GET" },
    handler = function(ctx)
        return Nixi.json({
            year = ctx.params.year,
            month = ctx.params.month,
            slug = ctx.params.slug
        })
    end,
}

Layouts

Routes can specify a layout to wrap their content:

-- layouts/default.lua
return function(data)
    local content = data.content or ""
    return [[
        <!DOCTYPE html>
        <html lang="en">
        <head>
            <title>]] .. (data.title or "Nixi App") .. [[</title>
            <link rel="stylesheet" href="/style.css">
        </head>
        <body>
            <nav>
                <a href="/">Home</a>
                <a href="/about">About</a>
            </nav>
            <main>
                ]] .. content .. [[
            </main>
        </body>
        </html>
    ]]
end

Using with app.lua

Routes are auto-loaded when the server starts:

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

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

-- Routes from routes/ are auto-loaded
-- Additional routes can be defined here
app:get("/api/status", function(ctx)
    return Nixi.json({ status = "ok" })
end)

return app